summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-x.ci/scripts/clang/docker.sh1
-rwxr-xr-x.ci/scripts/linux/docker.sh1
-rwxr-xr-x.ci/scripts/windows/docker.sh1
-rw-r--r--.gitmodules5
-rw-r--r--CMakeLists.txt24
-rw-r--r--dist/org.yuzu_emu.yuzu.desktop1
-rw-r--r--externals/CMakeLists.txt106
m---------externals/SDL0
m---------externals/Vulkan-Headers0
m---------externals/VulkanMemoryAllocator0
m---------externals/breakpad0
m---------externals/cpp-httplib0
m---------externals/cpp-jwt0
m---------externals/dynarmic0
m---------externals/ffmpeg/ffmpeg0
m---------externals/inih/inih0
-rw-r--r--externals/libusb/CMakeLists.txt7
m---------externals/libusb/libusb0
m---------externals/opus0
-rw-r--r--externals/opus/CMakeLists.txt259
m---------externals/opus/opus0
m---------externals/vcpkg0
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt62
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt28
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt17
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt46
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt17
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt17
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt20
-rw-r--r--src/android/app/src/main/jni/CMakeLists.txt2
-rw-r--r--src/android/app/src/main/jni/game_metadata.cpp112
-rw-r--r--src/android/app/src/main/jni/native.cpp757
-rw-r--r--src/android/app/src/main/jni/native.h84
-rw-r--r--src/android/app/src/main/res/layout/card_game.xml45
-rw-r--r--src/android/app/src/main/res/layout/card_home_option.xml3
-rw-r--r--src/common/fs/fs_android.cpp33
-rw-r--r--src/common/fs/fs_android.h15
-rw-r--r--src/common/fs/fs_paths.h1
-rw-r--r--src/common/fs/path_util.cpp11
-rw-r--r--src/common/fs/path_util.h1
-rw-r--r--src/common/nvidia_flags.cpp1
-rw-r--r--src/common/settings.h1
-rw-r--r--src/common/string_util.cpp12
-rw-r--r--src/core/arm/arm_interface.cpp6
-rw-r--r--src/core/core.cpp11
-rw-r--r--src/core/debugger/debugger.cpp24
-rw-r--r--src/core/debugger/gdbstub.cpp42
-rw-r--r--src/core/file_sys/program_metadata.cpp10
-rw-r--r--src/core/file_sys/program_metadata.h15
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp59
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/k_system_control.h13
-rw-r--r--src/core/hle/kernel/k_capabilities.h4
-rw-r--r--src/core/hle/kernel/k_condition_variable.cpp22
-rw-r--r--src/core/hle/kernel/k_condition_variable.h9
-rw-r--r--src/core/hle/kernel/k_interrupt_manager.cpp2
-rw-r--r--src/core/hle/kernel/k_memory_manager.cpp125
-rw-r--r--src/core/hle/kernel/k_memory_manager.h12
-rw-r--r--src/core/hle/kernel/k_page_table.cpp14
-rw-r--r--src/core/hle/kernel/k_page_table.h4
-rw-r--r--src/core/hle/kernel/k_process.cpp1442
-rw-r--r--src/core/hle/kernel/k_process.h724
-rw-r--r--src/core/hle/kernel/k_scheduler.cpp4
-rw-r--r--src/core/hle/kernel/k_system_resource.cpp87
-rw-r--r--src/core/hle/kernel/k_thread.cpp28
-rw-r--r--src/core/hle/kernel/k_thread.h1
-rw-r--r--src/core/hle/kernel/kernel.cpp53
-rw-r--r--src/core/hle/kernel/kernel.h3
-rw-r--r--src/core/hle/kernel/svc.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_info.cpp28
-rw-r--r--src/core/hle/kernel/svc/svc_lock.cpp4
-rw-r--r--src/core/hle/kernel/svc/svc_physical_memory.cpp4
-rw-r--r--src/core/hle/kernel/svc/svc_synchronization.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_thread.cpp7
-rw-r--r--src/core/hle/kernel/svc_generator.py2
-rw-r--r--src/core/hle/kernel/svc_types.h46
-rw-r--r--src/core/hle/service/am/am.cpp8
-rw-r--r--src/core/hle/service/am/applets/applet_cabinet.cpp4
-rw-r--r--src/core/hle/service/hid/controllers/palma.cpp4
-rw-r--r--src/core/hle/service/hid/hid.cpp4
-rw-r--r--src/core/hle/service/hid/hidbus/hidbus_base.cpp5
-rw-r--r--src/core/hle/service/kernel_helpers.cpp6
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_core.cpp6
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_producer.cpp5
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.cpp15
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.h3
-rw-r--r--src/core/hle/service/pctl/pctl_module.cpp6
-rw-r--r--src/core/hle/service/pm/pm.cpp2
-rw-r--r--src/core/reporter.cpp2
-rw-r--r--src/input_common/helpers/joycon_driver.cpp16
-rw-r--r--src/input_common/helpers/joycon_driver.h5
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h13
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp6
-rw-r--r--src/yuzu/CMakeLists.txt10
-rw-r--r--src/yuzu/applets/qt_controller.cpp52
-rw-r--r--src/yuzu/applets/qt_controller.h4
-rw-r--r--src/yuzu/breakpad.cpp77
-rw-r--r--src/yuzu/breakpad.h10
-rw-r--r--src/yuzu/configuration/configure_debug.cpp18
-rw-r--r--src/yuzu/configuration/configure_debug.ui7
-rw-r--r--src/yuzu/configuration/configure_input.cpp59
-rw-r--r--src/yuzu/configuration/configure_input.h7
-rw-r--r--src/yuzu/configuration/configure_input_player.h5
-rw-r--r--src/yuzu/debugger/wait_tree.cpp2
-rw-r--r--src/yuzu/game_list.cpp26
-rw-r--r--src/yuzu/game_list.h10
-rw-r--r--src/yuzu/game_list_worker.cpp102
-rw-r--r--src/yuzu/game_list_worker.h35
-rw-r--r--src/yuzu/main.cpp57
-rw-r--r--src/yuzu/main.h25
-rw-r--r--src/yuzu/mini_dump.cpp202
-rw-r--r--src/yuzu/mini_dump.h19
-rw-r--r--vcpkg.json4
119 files changed, 3106 insertions, 2256 deletions
diff --git a/.ci/scripts/clang/docker.sh b/.ci/scripts/clang/docker.sh
index 51769545e..f878e24e1 100755
--- a/.ci/scripts/clang/docker.sh
+++ b/.ci/scripts/clang/docker.sh
@@ -19,6 +19,7 @@ cmake .. \
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
-DENABLE_QT_TRANSLATION=ON \
-DUSE_DISCORD_PRESENCE=ON \
+ -DYUZU_CRASH_DUMPS=ON \
-DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \
-DYUZU_USE_BUNDLED_FFMPEG=ON \
-GNinja
diff --git a/.ci/scripts/linux/docker.sh b/.ci/scripts/linux/docker.sh
index a16577b27..7bba01d62 100755
--- a/.ci/scripts/linux/docker.sh
+++ b/.ci/scripts/linux/docker.sh
@@ -23,6 +23,7 @@ cmake .. \
-DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \
-DYUZU_USE_BUNDLED_FFMPEG=ON \
-DYUZU_ENABLE_LTO=ON \
+ -DYUZU_CRASH_DUMPS=ON \
-GNinja
ninja
diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh
index 45f75c874..44023600d 100755
--- a/.ci/scripts/windows/docker.sh
+++ b/.ci/scripts/windows/docker.sh
@@ -17,7 +17,6 @@ cmake .. \
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
-DENABLE_QT_TRANSLATION=ON \
-DUSE_CCACHE=ON \
- -DYUZU_CRASH_DUMPS=ON \
-DYUZU_USE_BUNDLED_SDL2=OFF \
-DYUZU_USE_EXTERNAL_SDL2=OFF \
-DYUZU_TESTS=OFF \
diff --git a/.gitmodules b/.gitmodules
index 361f4845b..b72a2ec8c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -32,7 +32,7 @@
path = externals/xbyak
url = https://github.com/herumi/xbyak.git
[submodule "opus"]
- path = externals/opus/opus
+ path = externals/opus
url = https://github.com/xiph/opus.git
[submodule "SDL"]
path = externals/SDL
@@ -58,3 +58,6 @@
[submodule "VulkanMemoryAllocator"]
path = externals/VulkanMemoryAllocator
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
+[submodule "breakpad"]
+ path = externals/breakpad
+ url = https://github.com/yuzu-emu/breakpad.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 150c78d64..9c35e0946 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -52,7 +52,7 @@ option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android"
CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF)
-CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF)
+CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF)
option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}")
@@ -139,9 +139,6 @@ if (YUZU_USE_BUNDLED_VCPKG)
if (YUZU_TESTS)
list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests")
endif()
- if (YUZU_CRASH_DUMPS)
- list(APPEND VCPKG_MANIFEST_FEATURES "dbghelp")
- endif()
if (ENABLE_WEB_SERVICE)
list(APPEND VCPKG_MANIFEST_FEATURES "web-service")
endif()
@@ -551,6 +548,18 @@ if (NOT YUZU_USE_BUNDLED_FFMPEG)
find_package(FFmpeg 4.3 REQUIRED QUIET COMPONENTS ${FFmpeg_COMPONENTS})
endif()
+if (WIN32 AND YUZU_CRASH_DUMPS)
+ set(BREAKPAD_VER "breakpad-c89f9dd")
+ download_bundled_external("breakpad/" ${BREAKPAD_VER} BREAKPAD_PREFIX)
+
+ set(BREAKPAD_CLIENT_INCLUDE_DIR "${BREAKPAD_PREFIX}/include")
+ set(BREAKPAD_CLIENT_LIBRARY "${BREAKPAD_PREFIX}/lib/libbreakpad_client.lib")
+
+ add_library(libbreakpad_client INTERFACE IMPORTED)
+ target_link_libraries(libbreakpad_client INTERFACE "${BREAKPAD_CLIENT_LIBRARY}")
+ target_include_directories(libbreakpad_client INTERFACE "${BREAKPAD_CLIENT_INCLUDE_DIR}")
+endif()
+
# Prefer the -pthread flag on Linux.
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
@@ -570,13 +579,6 @@ elseif (WIN32)
# PSAPI is the Process Status API
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)
endif()
-
- if (YUZU_CRASH_DUMPS)
- find_library(DBGHELP_LIBRARY dbghelp)
- if ("${DBGHELP_LIBRARY}" STREQUAL "DBGHELP_LIBRARY-NOTFOUND")
- message(FATAL_ERROR "YUZU_CRASH_DUMPS enabled but dbghelp library not found")
- endif()
- endif()
elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
set(PLATFORM_LIBRARIES rt)
endif()
diff --git a/dist/org.yuzu_emu.yuzu.desktop b/dist/org.yuzu_emu.yuzu.desktop
index 008422863..51e191a8e 100644
--- a/dist/org.yuzu_emu.yuzu.desktop
+++ b/dist/org.yuzu_emu.yuzu.desktop
@@ -13,3 +13,4 @@ Exec=yuzu %f
Categories=Game;Emulator;Qt;
MimeType=application/x-nx-nro;application/x-nx-nso;application/x-nx-nsp;application/x-nx-xci;
Keywords=Nintendo;Switch;
+StartupWMClass=yuzu
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 6e5bfbba6..be8b0b5e8 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -134,6 +134,10 @@ endif()
# Opus
if (NOT TARGET Opus::opus)
+ set(OPUS_BUILD_TESTING OFF)
+ set(OPUS_BUILD_PROGRAMS OFF)
+ set(OPUS_INSTALL_PKG_CONFIG_MODULE OFF)
+ set(OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF)
add_subdirectory(opus)
endif()
@@ -189,3 +193,105 @@ if (ANDROID)
add_subdirectory(libadrenotools)
endif()
endif()
+
+# Breakpad
+# https://github.com/microsoft/vcpkg/blob/master/ports/breakpad/CMakeLists.txt
+if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client)
+ set(BREAKPAD_WIN32_DEFINES
+ NOMINMAX
+ UNICODE
+ WIN32_LEAN_AND_MEAN
+ _CRT_SECURE_NO_WARNINGS
+ _CRT_SECURE_NO_DEPRECATE
+ _CRT_NONSTDC_NO_DEPRECATE
+ )
+
+ # libbreakpad
+ add_library(libbreakpad STATIC)
+ file(GLOB_RECURSE LIBBREAKPAD_SOURCES breakpad/src/processor/*.cc)
+ file(GLOB_RECURSE LIBDISASM_SOURCES breakpad/src/third_party/libdisasm/*.c)
+ list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "_unittest|_selftest|synth_minidump|/tests|/testdata|/solaris|microdump_stackwalk|minidump_dump|minidump_stackwalk")
+ if (WIN32)
+ list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "/linux|/mac|/android")
+ target_compile_definitions(libbreakpad PRIVATE ${BREAKPAD_WIN32_DEFINES})
+ target_include_directories(libbreakpad PRIVATE "${CMAKE_GENERATOR_INSTANCE}/DIA SDK/include")
+ elseif (APPLE)
+ list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "/linux|/windows|/android")
+ else()
+ list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "/mac|/windows|/android")
+ endif()
+ target_sources(libbreakpad PRIVATE ${LIBBREAKPAD_SOURCES} ${LIBDISASM_SOURCES})
+ target_include_directories(libbreakpad
+ PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src
+ ${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src/third_party/libdisasm
+ )
+
+ # libbreakpad_client
+ add_library(libbreakpad_client STATIC)
+ file(GLOB LIBBREAKPAD_COMMON_SOURCES breakpad/src/common/*.cc breakpad/src/common/*.c breakpad/src/client/*.cc)
+
+ if (WIN32)
+ file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/windows/*.cc breakpad/src/common/windows/*.cc)
+ list(FILTER LIBBREAKPAD_COMMON_SOURCES EXCLUDE REGEX "language.cc|path_helper.cc|stabs_to_module.cc|stabs_reader.cc|minidump_file_writer.cc")
+ target_include_directories(libbreakpad_client PRIVATE "${CMAKE_GENERATOR_INSTANCE}/DIA SDK/include")
+ target_compile_definitions(libbreakpad_client PRIVATE ${BREAKPAD_WIN32_DEFINES})
+ elseif (APPLE)
+ target_compile_definitions(libbreakpad_client PRIVATE HAVE_MACH_O_NLIST_H)
+ file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/mac/*.cc breakpad/src/common/mac/*.cc)
+ list(APPEND LIBBREAKPAD_CLIENT_SOURCES breakpad/src/common/mac/MachIPC.mm)
+ else()
+ target_compile_definitions(libbreakpad_client PUBLIC -DHAVE_A_OUT_H)
+ file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/linux/*.cc breakpad/src/common/linux/*.cc)
+ endif()
+ list(APPEND LIBBREAKPAD_CLIENT_SOURCES ${LIBBREAKPAD_COMMON_SOURCES})
+ list(FILTER LIBBREAKPAD_CLIENT_SOURCES EXCLUDE REGEX "/sender|/tests|/unittests|/testcases|_unittest|_test")
+ target_sources(libbreakpad_client PRIVATE ${LIBBREAKPAD_CLIENT_SOURCES})
+ target_include_directories(libbreakpad_client PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src)
+
+ if (WIN32)
+ target_link_libraries(libbreakpad_client PRIVATE wininet.lib)
+ elseif (APPLE)
+ find_library(CoreFoundation_FRAMEWORK CoreFoundation)
+ target_link_libraries(libbreakpad_client PRIVATE ${CoreFoundation_FRAMEWORK})
+ else()
+ find_library(PTHREAD_LIBRARIES pthread)
+ target_compile_definitions(libbreakpad_client PRIVATE HAVE_GETCONTEXT=1)
+ if (PTHREAD_LIBRARIES)
+ target_link_libraries(libbreakpad_client PRIVATE ${PTHREAD_LIBRARIES})
+ endif()
+ endif()
+
+ # Host tools for symbol processing
+ if (LINUX)
+ find_package(ZLIB REQUIRED)
+
+ add_executable(minidump_stackwalk breakpad/src/processor/minidump_stackwalk.cc)
+ target_link_libraries(minidump_stackwalk PRIVATE libbreakpad libbreakpad_client)
+
+ add_executable(dump_syms
+ breakpad/src/common/dwarf_cfi_to_module.cc
+ breakpad/src/common/dwarf_cu_to_module.cc
+ breakpad/src/common/dwarf_line_to_module.cc
+ breakpad/src/common/dwarf_range_list_handler.cc
+ breakpad/src/common/language.cc
+ breakpad/src/common/module.cc
+ breakpad/src/common/path_helper.cc
+ breakpad/src/common/stabs_reader.cc
+ breakpad/src/common/stabs_to_module.cc
+ breakpad/src/common/dwarf/bytereader.cc
+ breakpad/src/common/dwarf/dwarf2diehandler.cc
+ breakpad/src/common/dwarf/dwarf2reader.cc
+ breakpad/src/common/dwarf/elf_reader.cc
+ breakpad/src/common/linux/crc32.cc
+ breakpad/src/common/linux/dump_symbols.cc
+ breakpad/src/common/linux/elf_symbols_to_module.cc
+ breakpad/src/common/linux/elfutils.cc
+ breakpad/src/common/linux/file_id.cc
+ breakpad/src/common/linux/linux_libc_support.cc
+ breakpad/src/common/linux/memory_mapped_file.cc
+ breakpad/src/common/linux/safe_readlink.cc
+ breakpad/src/tools/linux/dump_syms/dump_syms.cc)
+ target_link_libraries(dump_syms PRIVATE libbreakpad_client ZLIB::ZLIB)
+ endif()
+endif()
diff --git a/externals/SDL b/externals/SDL
-Subproject 031912c4b6c5db80b443f04aa56fec3e4e64515
+Subproject cc016b0046d563287f0aa9f09b958b5e70d4369
diff --git a/externals/Vulkan-Headers b/externals/Vulkan-Headers
-Subproject ed857118e243fdc0f3a100f00ac9919e874cfe6
+Subproject df60f0316899460eeaaefa06d2dd7e4e300c160
diff --git a/externals/VulkanMemoryAllocator b/externals/VulkanMemoryAllocator
-Subproject 9b0fc3e7b02afe97895eb3e945fe800c3a7485a
+Subproject 2f382df218d7e8516dee3b3caccb819a62b571a
diff --git a/externals/breakpad b/externals/breakpad
new file mode 160000
+Subproject c89f9dddc793f19910ef06c13e4fd240da4e7a5
diff --git a/externals/cpp-httplib b/externals/cpp-httplib
-Subproject 6d963fbe8d415399d65e94db7910bbd22fe3741
+Subproject a609330e4c6374f741d3b369269f7848255e195
diff --git a/externals/cpp-jwt b/externals/cpp-jwt
-Subproject e12ef06218596b52d9b5d6e1639484866a8e706
+Subproject 10ef5735d842b31025f1257ae78899f50a40fb1
diff --git a/externals/dynarmic b/externals/dynarmic
-Subproject 7da378033a7764f955516f75194856d87bbcd7a
+Subproject 0df09e2f6b61c2d7ad2f2053d4f020a5c33e037
diff --git a/externals/ffmpeg/ffmpeg b/externals/ffmpeg/ffmpeg
-Subproject 6b6b9e593dd4d3aaf75f48d40a13ef03bdef9fd
+Subproject 9c1294eaddb88cb0e044c675ccae059a85fc9c6
diff --git a/externals/inih/inih b/externals/inih/inih
-Subproject 1e80a47dffbda813604f0913e2ad68c7054c14e
+Subproject 9cecf0643da0846e77f64d10a126d9f48b9e05e
diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt
index 6757b59da..1d50c9f8c 100644
--- a/externals/libusb/CMakeLists.txt
+++ b/externals/libusb/CMakeLists.txt
@@ -49,11 +49,6 @@ if (MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux") OR APPLE)
set(LIBUSB_INCLUDE_DIRS "${LIBUSB_SRC_DIR}/libusb" CACHE PATH "libusb headers path" FORCE)
- # MINGW: causes "externals/libusb/libusb/libusb/os/windows_winusb.c:1427:2: error: conversion to non-scalar type requested", so cannot statically link it for now.
- if (NOT MINGW)
- set(LIBUSB_CFLAGS "-DGUID_DEVINTERFACE_USB_DEVICE=\\(GUID\\){0xA5DCBF10,0x6530,0x11D2,{0x90,0x1F,0x00,0xC0,0x4F,0xB9,0x51,0xED}}")
- endif()
-
make_directory("${LIBUSB_PREFIX}")
add_custom_command(
@@ -146,8 +141,6 @@ else() # MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
target_include_directories(usb BEFORE PRIVATE libusb/msvc)
endif()
- # Works around other libraries providing their own definition of USB GUIDs (e.g. SDL2)
- target_compile_definitions(usb PRIVATE "-DGUID_DEVINTERFACE_USB_DEVICE=(GUID){ 0xA5DCBF10, 0x6530, 0x11D2, {0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED}}")
else()
target_include_directories(usb
# turns out other projects also have "config.h", so make sure the
diff --git a/externals/libusb/libusb b/externals/libusb/libusb
-Subproject c6a35c56016ea2ab2f19115d2ea1e85e0edae15
+Subproject c060e9ce30ac2e3ffb49d94209c4dae77b6642f
diff --git a/externals/opus b/externals/opus
new file mode 160000
+Subproject 101a71e03bbf860aaafb7090a0e440675cb2766
diff --git a/externals/opus/CMakeLists.txt b/externals/opus/CMakeLists.txt
deleted file mode 100644
index d9a03423d..000000000
--- a/externals/opus/CMakeLists.txt
+++ /dev/null
@@ -1,259 +0,0 @@
-# SPDX-FileCopyrightText: 2019 yuzu Emulator Project
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-cmake_minimum_required(VERSION 3.8)
-
-project(opus)
-
-option(OPUS_STACK_PROTECTOR "Use stack protection" OFF)
-option(OPUS_USE_ALLOCA "Use alloca for stack arrays (on non-C99 compilers)" OFF)
-option(OPUS_CUSTOM_MODES "Enable non-Opus modes, e.g. 44.1 kHz & 2^n frames" OFF)
-option(OPUS_FIXED_POINT "Compile as fixed-point (for machines without a fast enough FPU)" OFF)
-option(OPUS_ENABLE_FLOAT_API "Compile with the floating point API (for machines with float library" ON)
-
-include(opus/opus_functions.cmake)
-
-if(OPUS_STACK_PROTECTOR)
- if(NOT MSVC) # GC on by default on MSVC
- check_and_set_flag(STACK_PROTECTION_STRONG -fstack-protector-strong)
- endif()
-else()
- if(MSVC)
- check_and_set_flag(BUFFER_SECURITY_CHECK /GS-)
- endif()
-endif()
-
-add_library(opus
- # CELT sources
- opus/celt/bands.c
- opus/celt/celt.c
- opus/celt/celt_decoder.c
- opus/celt/celt_encoder.c
- opus/celt/celt_lpc.c
- opus/celt/cwrs.c
- opus/celt/entcode.c
- opus/celt/entdec.c
- opus/celt/entenc.c
- opus/celt/kiss_fft.c
- opus/celt/laplace.c
- opus/celt/mathops.c
- opus/celt/mdct.c
- opus/celt/modes.c
- opus/celt/pitch.c
- opus/celt/quant_bands.c
- opus/celt/rate.c
- opus/celt/vq.c
-
- # SILK sources
- opus/silk/A2NLSF.c
- opus/silk/CNG.c
- opus/silk/HP_variable_cutoff.c
- opus/silk/LPC_analysis_filter.c
- opus/silk/LPC_fit.c
- opus/silk/LPC_inv_pred_gain.c
- opus/silk/LP_variable_cutoff.c
- opus/silk/NLSF2A.c
- opus/silk/NLSF_VQ.c
- opus/silk/NLSF_VQ_weights_laroia.c
- opus/silk/NLSF_decode.c
- opus/silk/NLSF_del_dec_quant.c
- opus/silk/NLSF_encode.c
- opus/silk/NLSF_stabilize.c
- opus/silk/NLSF_unpack.c
- opus/silk/NSQ.c
- opus/silk/NSQ_del_dec.c
- opus/silk/PLC.c
- opus/silk/VAD.c
- opus/silk/VQ_WMat_EC.c
- opus/silk/ana_filt_bank_1.c
- opus/silk/biquad_alt.c
- opus/silk/bwexpander.c
- opus/silk/bwexpander_32.c
- opus/silk/check_control_input.c
- opus/silk/code_signs.c
- opus/silk/control_SNR.c
- opus/silk/control_audio_bandwidth.c
- opus/silk/control_codec.c
- opus/silk/dec_API.c
- opus/silk/decode_core.c
- opus/silk/decode_frame.c
- opus/silk/decode_indices.c
- opus/silk/decode_parameters.c
- opus/silk/decode_pitch.c
- opus/silk/decode_pulses.c
- opus/silk/decoder_set_fs.c
- opus/silk/enc_API.c
- opus/silk/encode_indices.c
- opus/silk/encode_pulses.c
- opus/silk/gain_quant.c
- opus/silk/init_decoder.c
- opus/silk/init_encoder.c
- opus/silk/inner_prod_aligned.c
- opus/silk/interpolate.c
- opus/silk/lin2log.c
- opus/silk/log2lin.c
- opus/silk/pitch_est_tables.c
- opus/silk/process_NLSFs.c
- opus/silk/quant_LTP_gains.c
- opus/silk/resampler.c
- opus/silk/resampler_down2.c
- opus/silk/resampler_down2_3.c
- opus/silk/resampler_private_AR2.c
- opus/silk/resampler_private_IIR_FIR.c
- opus/silk/resampler_private_down_FIR.c
- opus/silk/resampler_private_up2_HQ.c
- opus/silk/resampler_rom.c
- opus/silk/shell_coder.c
- opus/silk/sigm_Q15.c
- opus/silk/sort.c
- opus/silk/stereo_LR_to_MS.c
- opus/silk/stereo_MS_to_LR.c
- opus/silk/stereo_decode_pred.c
- opus/silk/stereo_encode_pred.c
- opus/silk/stereo_find_predictor.c
- opus/silk/stereo_quant_pred.c
- opus/silk/sum_sqr_shift.c
- opus/silk/table_LSF_cos.c
- opus/silk/tables_LTP.c
- opus/silk/tables_NLSF_CB_NB_MB.c
- opus/silk/tables_NLSF_CB_WB.c
- opus/silk/tables_gain.c
- opus/silk/tables_other.c
- opus/silk/tables_pitch_lag.c
- opus/silk/tables_pulses_per_block.c
-
- # Opus sources
- opus/src/analysis.c
- opus/src/mapping_matrix.c
- opus/src/mlp.c
- opus/src/mlp_data.c
- opus/src/opus.c
- opus/src/opus_decoder.c
- opus/src/opus_encoder.c
- opus/src/opus_multistream.c
- opus/src/opus_multistream_decoder.c
- opus/src/opus_multistream_encoder.c
- opus/src/opus_projection_decoder.c
- opus/src/opus_projection_encoder.c
- opus/src/repacketizer.c
-)
-
-if (DEBUG)
- target_sources(opus PRIVATE opus/silk/debug.c)
-endif()
-
-if (OPUS_FIXED_POINT)
- target_sources(opus PRIVATE
- opus/silk/fixed/LTP_analysis_filter_FIX.c
- opus/silk/fixed/LTP_scale_ctrl_FIX.c
- opus/silk/fixed/apply_sine_window_FIX.c
- opus/silk/fixed/autocorr_FIX.c
- opus/silk/fixed/burg_modified_FIX.c
- opus/silk/fixed/corrMatrix_FIX.c
- opus/silk/fixed/encode_frame_FIX.c
- opus/silk/fixed/find_LPC_FIX.c
- opus/silk/fixed/find_LTP_FIX.c
- opus/silk/fixed/find_pitch_lags_FIX.c
- opus/silk/fixed/find_pred_coefs_FIX.c
- opus/silk/fixed/k2a_FIX.c
- opus/silk/fixed/k2a_Q16_FIX.c
- opus/silk/fixed/noise_shape_analysis_FIX.c
- opus/silk/fixed/pitch_analysis_core_FIX.c
- opus/silk/fixed/prefilter_FIX.c
- opus/silk/fixed/process_gains_FIX.c
- opus/silk/fixed/regularize_correlations_FIX.c
- opus/silk/fixed/residual_energy16_FIX.c
- opus/silk/fixed/residual_energy_FIX.c
- opus/silk/fixed/schur64_FIX.c
- opus/silk/fixed/schur_FIX.c
- opus/silk/fixed/solve_LS_FIX.c
- opus/silk/fixed/vector_ops_FIX.c
- opus/silk/fixed/warped_autocorrelation_FIX.c
- )
-else()
- target_sources(opus PRIVATE
- opus/silk/float/LPC_analysis_filter_FLP.c
- opus/silk/float/LPC_inv_pred_gain_FLP.c
- opus/silk/float/LTP_analysis_filter_FLP.c
- opus/silk/float/LTP_scale_ctrl_FLP.c
- opus/silk/float/apply_sine_window_FLP.c
- opus/silk/float/autocorrelation_FLP.c
- opus/silk/float/burg_modified_FLP.c
- opus/silk/float/bwexpander_FLP.c
- opus/silk/float/corrMatrix_FLP.c
- opus/silk/float/encode_frame_FLP.c
- opus/silk/float/energy_FLP.c
- opus/silk/float/find_LPC_FLP.c
- opus/silk/float/find_LTP_FLP.c
- opus/silk/float/find_pitch_lags_FLP.c
- opus/silk/float/find_pred_coefs_FLP.c
- opus/silk/float/inner_product_FLP.c
- opus/silk/float/k2a_FLP.c
- opus/silk/float/noise_shape_analysis_FLP.c
- opus/silk/float/pitch_analysis_core_FLP.c
- opus/silk/float/process_gains_FLP.c
- opus/silk/float/regularize_correlations_FLP.c
- opus/silk/float/residual_energy_FLP.c
- opus/silk/float/scale_copy_vector_FLP.c
- opus/silk/float/scale_vector_FLP.c
- opus/silk/float/schur_FLP.c
- opus/silk/float/sort_FLP.c
- opus/silk/float/warped_autocorrelation_FLP.c
- opus/silk/float/wrappers_FLP.c
- )
-endif()
-
-target_compile_definitions(opus PRIVATE OPUS_BUILD ENABLE_HARDENING)
-
-if(NOT MSVC)
- if(MINGW)
- target_compile_definitions(opus PRIVATE _FORTIFY_SOURCE=0)
- else()
- target_compile_definitions(opus PRIVATE _FORTIFY_SOURCE=2)
- endif()
-endif()
-
-# It is strongly recommended to uncomment one of these VAR_ARRAYS: Use C99
-# variable-length arrays for stack allocation USE_ALLOCA: Use alloca() for stack
-# allocation If none is defined, then the fallback is a non-threadsafe global
-# array
-if(OPUS_USE_ALLOCA OR MSVC)
- target_compile_definitions(opus PRIVATE USE_ALLOCA)
-else()
- target_compile_definitions(opus PRIVATE VAR_ARRAYS)
-endif()
-
-if(OPUS_CUSTOM_MODES)
- target_compile_definitions(opus PRIVATE CUSTOM_MODES)
-endif()
-
-if(NOT OPUS_ENABLE_FLOAT_API)
- target_compile_definitions(opus PRIVATE DISABLE_FLOAT_API)
-endif()
-
-target_compile_definitions(opus
-PUBLIC
- -DOPUS_VERSION="\\"1.3.1\\""
-
-PRIVATE
- # Use C99 intrinsics to speed up float-to-int conversion
- HAVE_LRINTF
-)
-
-if (FIXED_POINT)
- target_compile_definitions(opus PRIVATE -DFIXED_POINT=1 -DDISABLE_FLOAT_API)
-endif()
-
-target_include_directories(opus
-PUBLIC
- opus/include
-
-PRIVATE
- opus/celt
- opus/silk
- opus/silk/fixed
- opus/silk/float
- opus/src
-)
-
-add_library(Opus::opus ALIAS opus)
diff --git a/externals/opus/opus b/externals/opus/opus
deleted file mode 160000
-Subproject ad8fe90db79b7d2a135e3dfd2ed6631b0c5662a
diff --git a/externals/vcpkg b/externals/vcpkg
-Subproject cbf56573a987527b39272e88cbdd11389b78c6e
+Subproject ef2eef17340f3fbd679327d286fad06dd6e838e
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index 115f72710..e2c5b6acd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -5,6 +5,7 @@ package org.yuzu.yuzu_emu
import android.app.Dialog
import android.content.DialogInterface
+import android.net.Uri
import android.os.Bundle
import android.text.Html
import android.text.method.LinkMovementMethod
@@ -16,7 +17,7 @@ import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.ref.WeakReference
import org.yuzu.yuzu_emu.activities.EmulationActivity
-import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
+import org.yuzu.yuzu_emu.utils.DocumentsTree
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
@@ -68,7 +69,7 @@ object NativeLibrary {
@Keep
@JvmStatic
fun openContentUri(path: String?, openmode: String?): Int {
- return if (isNativePath(path!!)) {
+ return if (DocumentsTree.isNativePath(path!!)) {
YuzuApplication.documentsTree!!.openContentUri(path, openmode)
} else {
FileUtil.openContentUri(path, openmode)
@@ -78,7 +79,7 @@ object NativeLibrary {
@Keep
@JvmStatic
fun getSize(path: String?): Long {
- return if (isNativePath(path!!)) {
+ return if (DocumentsTree.isNativePath(path!!)) {
YuzuApplication.documentsTree!!.getFileSize(path)
} else {
FileUtil.getFileSize(path)
@@ -88,23 +89,41 @@ object NativeLibrary {
@Keep
@JvmStatic
fun exists(path: String?): Boolean {
- return if (isNativePath(path!!)) {
+ return if (DocumentsTree.isNativePath(path!!)) {
YuzuApplication.documentsTree!!.exists(path)
} else {
- FileUtil.exists(path)
+ FileUtil.exists(path, suppressLog = true)
}
}
@Keep
@JvmStatic
fun isDirectory(path: String?): Boolean {
- return if (isNativePath(path!!)) {
+ return if (DocumentsTree.isNativePath(path!!)) {
YuzuApplication.documentsTree!!.isDirectory(path)
} else {
FileUtil.isDirectory(path)
}
}
+ @Keep
+ @JvmStatic
+ fun getParentDirectory(path: String): String =
+ if (DocumentsTree.isNativePath(path)) {
+ YuzuApplication.documentsTree!!.getParentDirectory(path)
+ } else {
+ path
+ }
+
+ @Keep
+ @JvmStatic
+ fun getFilename(path: String): String =
+ if (DocumentsTree.isNativePath(path)) {
+ YuzuApplication.documentsTree!!.getFilename(path)
+ } else {
+ FileUtil.getFilename(Uri.parse(path))
+ }
+
/**
* Returns true if pro controller isn't available and handheld is
*/
@@ -215,32 +234,6 @@ object NativeLibrary {
external fun initGameIni(gameID: String?)
- /**
- * Gets the embedded icon within the given ROM.
- *
- * @param filename the file path to the ROM.
- * @return a byte array containing the JPEG data for the icon.
- */
- external fun getIcon(filename: String): ByteArray
-
- /**
- * Gets the embedded title of the given ISO/ROM.
- *
- * @param filename The file path to the ISO/ROM.
- * @return the embedded title of the ISO/ROM.
- */
- external fun getTitle(filename: String): String
-
- external fun getDescription(filename: String): String
-
- external fun getGameId(filename: String): String
-
- external fun getRegions(filename: String): String
-
- external fun getCompany(filename: String): String
-
- external fun isHomebrew(filename: String): Boolean
-
external fun setAppDirectory(directory: String)
/**
@@ -294,11 +287,6 @@ object NativeLibrary {
external fun stopEmulation()
/**
- * Resets the in-memory ROM metadata cache.
- */
- external fun resetRomMetadata()
-
- /**
* Returns true if emulation is running (or is paused).
*/
external fun isRunning(): Boolean
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
index f9f88a1d2..0c82cdba8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
@@ -147,7 +147,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
- return oldItem.gameId == newItem.gameId
+ return oldItem.programId == newItem.programId
}
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 598a9d42b..07bd78bf7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -15,6 +15,7 @@ import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
+import android.os.SystemClock
import android.view.*
import android.widget.TextView
import android.widget.Toast
@@ -25,6 +26,7 @@ import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.drawerlayout.widget.DrawerLayout
+import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
@@ -156,6 +158,32 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.showFpsText.setTextColor(Color.YELLOW)
binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
+ binding.drawerLayout.addDrawerListener(object : DrawerListener {
+ override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
+ binding.surfaceInputOverlay.dispatchTouchEvent(
+ MotionEvent.obtain(
+ SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis() + 100,
+ MotionEvent.ACTION_UP,
+ 0f,
+ 0f,
+ 0
+ )
+ )
+ }
+
+ override fun onDrawerOpened(drawerView: View) {
+ // No op
+ }
+
+ override fun onDrawerClosed(drawerView: View) {
+ // No op
+ }
+
+ override fun onDrawerStateChanged(newState: Int) {
+ // No op
+ }
+ })
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
game.title
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index fd9785075..f273c880a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -26,7 +26,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
-import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.BuildConfig
import org.yuzu.yuzu_emu.HomeNavigationDirections
@@ -186,7 +186,8 @@ class HomeSettingsFragment : Fragment() {
}
binding.homeSettingsList.apply {
- layoutManager = LinearLayoutManager(requireContext())
+ layoutManager =
+ GridLayoutManager(requireContext(), resources.getInteger(R.integer.grid_columns))
adapter = HomeSettingAdapter(
requireActivity() as AppCompatActivity,
viewLifecycleOwner,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
index 6527c64ab..b43978fce 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
@@ -12,15 +12,14 @@ import kotlinx.serialization.Serializable
@Serializable
class Game(
val title: String,
- val description: String,
- val regions: String,
val path: String,
- val gameId: String,
- val company: String,
+ val programId: String,
+ val developer: String,
+ val version: String,
val isHomebrew: Boolean
) : Parcelable {
- val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime"
- val keyLastPlayedTime get() = "${gameId}_LastPlayed"
+ val keyAddedToLibraryTime get() = "${programId}_AddedToLibraryTime"
+ val keyLastPlayedTime get() = "${programId}_LastPlayed"
override fun equals(other: Any?): Boolean {
if (other !is Game) {
@@ -32,11 +31,9 @@ class Game(
override fun hashCode(): Int {
var result = title.hashCode()
- result = 31 * result + description.hashCode()
- result = 31 * result + regions.hashCode()
result = 31 * result + path.hashCode()
- result = 31 * result + gameId.hashCode()
- result = 31 * result + company.hashCode()
+ result = 31 * result + programId.hashCode()
+ result = 31 * result + developer.hashCode()
result = 31 * result + isHomebrew.hashCode()
return result
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
index 6e09fa81d..8512ed17c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
@@ -14,15 +14,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import kotlinx.serialization.ExperimentalSerializationApi
-import kotlinx.serialization.MissingFieldException
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper
+import org.yuzu.yuzu_emu.utils.GameMetadata
-@OptIn(ExperimentalSerializationApi::class)
class GamesViewModel : ViewModel() {
val games: StateFlow<List<Game>> get() = _games
private val _games = MutableStateFlow(emptyList<Game>())
@@ -49,26 +47,34 @@ class GamesViewModel : ViewModel() {
// Retrieve list of cached games
val storedGames = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
.getStringSet(GameHelper.KEY_GAMES, emptySet())
- if (storedGames!!.isNotEmpty()) {
- val deserializedGames = mutableSetOf<Game>()
- storedGames.forEach {
- val game: Game
- try {
- game = Json.decodeFromString(it)
- } catch (e: MissingFieldException) {
- return@forEach
- }
- val gameExists =
- DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(game.path))
- ?.exists()
- if (gameExists == true) {
- deserializedGames.add(game)
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ if (storedGames!!.isNotEmpty()) {
+ val deserializedGames = mutableSetOf<Game>()
+ storedGames.forEach {
+ val game: Game
+ try {
+ game = Json.decodeFromString(it)
+ } catch (e: Exception) {
+ // We don't care about any errors related to parsing the game cache
+ return@forEach
+ }
+
+ val gameExists =
+ DocumentFile.fromSingleUri(
+ YuzuApplication.appContext,
+ Uri.parse(game.path)
+ )?.exists()
+ if (gameExists == true) {
+ deserializedGames.add(game)
+ }
+ }
+ setGames(deserializedGames.toList())
}
+ reloadGames(false)
}
- setGames(deserializedGames.toList())
}
- reloadGames(false)
}
fun setGames(games: List<Game>) {
@@ -106,7 +112,7 @@ class GamesViewModel : ViewModel() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
- NativeLibrary.resetRomMetadata()
+ GameMetadata.resetMetadata()
setGames(GameHelper.getGames())
_isReloading.value = false
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
index eafcf9e42..738275297 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
@@ -42,6 +42,23 @@ class DocumentsTree {
return node != null && node.isDirectory
}
+ fun getParentDirectory(filepath: String): String {
+ val node = resolvePath(filepath)!!
+ val parentNode = node.parent
+ if (parentNode != null && parentNode.isDirectory) {
+ return parentNode.uri!!.toString()
+ }
+ return node.uri!!.toString()
+ }
+
+ fun getFilename(filepath: String): String {
+ val node = resolvePath(filepath)
+ if (node != null) {
+ return node.name!!
+ }
+ return filepath
+ }
+
private fun resolvePath(filepath: String): DocumentsNode? {
val tokens = StringTokenizer(filepath, File.separator, false)
var iterator = root
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
index 5ee74a52c..8c3268e9c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
@@ -144,7 +144,7 @@ object FileUtil {
* @param path Native content uri path
* @return bool
*/
- fun exists(path: String?): Boolean {
+ fun exists(path: String?, suppressLog: Boolean = false): Boolean {
var c: Cursor? = null
try {
val mUri = Uri.parse(path)
@@ -152,7 +152,9 @@ object FileUtil {
c = context.contentResolver.query(mUri, columns, null, null, null)
return c!!.count > 0
} catch (e: Exception) {
- Log.info("[FileUtil] Cannot find file from given path, error: " + e.message)
+ if (!suppressLog) {
+ Log.info("[FileUtil] Cannot find file from given path, error: " + e.message)
+ }
} finally {
closeQuietly(c)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
index 9001ca9ab..e6aca6b44 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
@@ -71,27 +71,26 @@ object GameHelper {
fun getGame(uri: Uri, addedToLibrary: Boolean): Game {
val filePath = uri.toString()
- var name = NativeLibrary.getTitle(filePath)
+ var name = GameMetadata.getTitle(filePath)
// If the game's title field is empty, use the filename.
if (name.isEmpty()) {
name = FileUtil.getFilename(uri)
}
- var gameId = NativeLibrary.getGameId(filePath)
+ var programId = GameMetadata.getProgramId(filePath)
// If the game's ID field is empty, use the filename without extension.
- if (gameId.isEmpty()) {
- gameId = name.substring(0, name.lastIndexOf("."))
+ if (programId.isEmpty()) {
+ programId = name.substring(0, name.lastIndexOf("."))
}
val newGame = Game(
name,
- NativeLibrary.getDescription(filePath).replace("\n", " "),
- NativeLibrary.getRegions(filePath),
filePath,
- gameId,
- NativeLibrary.getCompany(filePath),
- NativeLibrary.isHomebrew(filePath)
+ programId,
+ GameMetadata.getDeveloper(filePath),
+ GameMetadata.getVersion(filePath),
+ GameMetadata.getIsHomebrew(filePath)
)
if (addedToLibrary) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
index 9fe99fab1..654d62f52 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
@@ -18,7 +18,6 @@ import coil.key.Keyer
import coil.memory.MemoryCache
import coil.request.ImageRequest
import coil.request.Options
-import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.Game
@@ -36,7 +35,7 @@ class GameIconFetcher(
}
private fun decodeGameIcon(uri: String): Bitmap? {
- val data = NativeLibrary.getIcon(uri)
+ val data = GameMetadata.getIcon(uri)
return BitmapFactory.decodeByteArray(
data,
0,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt
new file mode 100644
index 000000000..0f3542ac6
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+object GameMetadata {
+ external fun getTitle(path: String): String
+
+ external fun getProgramId(path: String): String
+
+ external fun getDeveloper(path: String): String
+
+ external fun getVersion(path: String): String
+
+ external fun getIcon(path: String): ByteArray
+
+ external fun getIsHomebrew(path: String): Boolean
+
+ external fun resetMetadata()
+}
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt
index e15d1480b..1c36661f5 100644
--- a/src/android/app/src/main/jni/CMakeLists.txt
+++ b/src/android/app/src/main/jni/CMakeLists.txt
@@ -14,8 +14,10 @@ add_library(yuzu-android SHARED
id_cache.cpp
id_cache.h
native.cpp
+ native.h
native_config.cpp
uisettings.cpp
+ game_metadata.cpp
)
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
diff --git a/src/android/app/src/main/jni/game_metadata.cpp b/src/android/app/src/main/jni/game_metadata.cpp
new file mode 100644
index 000000000..24d9df702
--- /dev/null
+++ b/src/android/app/src/main/jni/game_metadata.cpp
@@ -0,0 +1,112 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <core/core.h>
+#include <core/file_sys/patch_manager.h>
+#include <core/loader/nro.h>
+#include <jni.h>
+#include "core/loader/loader.h"
+#include "jni/android_common/android_common.h"
+#include "native.h"
+
+struct RomMetadata {
+ std::string title;
+ u64 programId;
+ std::string developer;
+ std::string version;
+ std::vector<u8> icon;
+ bool isHomebrew;
+};
+
+std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache;
+
+RomMetadata CacheRomMetadata(const std::string& path) {
+ const auto file =
+ Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path);
+ auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
+
+ RomMetadata entry;
+ loader->ReadTitle(entry.title);
+ loader->ReadProgramId(entry.programId);
+ loader->ReadIcon(entry.icon);
+
+ const FileSys::PatchManager pm{
+ entry.programId, EmulationSession::GetInstance().System().GetFileSystemController(),
+ EmulationSession::GetInstance().System().GetContentProvider()};
+ const auto control = pm.GetControlMetadata();
+
+ if (control.first != nullptr) {
+ entry.developer = control.first->GetDeveloperName();
+ entry.version = control.first->GetVersionString();
+ } else {
+ FileSys::NACP nacp;
+ if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) {
+ entry.developer = nacp.GetDeveloperName();
+ } else {
+ entry.developer = "";
+ }
+
+ entry.version = "1.0.0";
+ }
+
+ if (loader->GetFileType() == Loader::FileType::NRO) {
+ auto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
+ entry.isHomebrew = loader_nro->IsHomebrew();
+ } else {
+ entry.isHomebrew = false;
+ }
+
+ m_rom_metadata_cache[path] = entry;
+
+ return entry;
+}
+
+RomMetadata GetRomMetadata(const std::string& path) {
+ if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
+ return search->second;
+ }
+
+ return CacheRomMetadata(path);
+}
+
+extern "C" {
+
+jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj,
+ jstring jpath) {
+ return ToJString(env, GetRomMetadata(GetJString(env, jpath)).title);
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj,
+ jstring jpath) {
+ return ToJString(env, std::to_string(GetRomMetadata(GetJString(env, jpath)).programId));
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj,
+ jstring jpath) {
+ return ToJString(env, GetRomMetadata(GetJString(env, jpath)).developer);
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj,
+ jstring jpath) {
+ return ToJString(env, GetRomMetadata(GetJString(env, jpath)).version);
+}
+
+jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj,
+ jstring jpath) {
+ auto icon_data = GetRomMetadata(GetJString(env, jpath)).icon;
+ jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
+ env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
+ reinterpret_cast<jbyte*>(icon_data.data()));
+ return icon;
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj,
+ jstring jpath) {
+ return static_cast<jboolean>(GetRomMetadata(GetJString(env, jpath)).isHomebrew);
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_GameMetadata_resetMetadata(JNIEnv* env, jobject obj) {
+ return m_rom_metadata_cache.clear();
+}
+
+} // extern "C"
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 598f4e8bf..686b73588 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -33,7 +33,6 @@
#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
-#include "core/file_sys/registered_cache.h"
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
@@ -48,514 +47,416 @@
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
-#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
-#include "core/perf_stats.h"
#include "jni/android_common/android_common.h"
-#include "jni/applets/software_keyboard.h"
#include "jni/config.h"
-#include "jni/emu_window/emu_window.h"
#include "jni/id_cache.h"
-#include "video_core/rasterizer_interface.h"
+#include "jni/native.h"
#include "video_core/renderer_base.h"
#define jconst [[maybe_unused]] const auto
#define jauto [[maybe_unused]] auto
-namespace {
+static EmulationSession s_instance;
-class EmulationSession final {
-public:
- EmulationSession() {
- m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
- }
-
- ~EmulationSession() = default;
-
- static EmulationSession& GetInstance() {
- return s_instance;
- }
-
- const Core::System& System() const {
- return m_system;
- }
+EmulationSession::EmulationSession() {
+ m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
+}
- Core::System& System() {
- return m_system;
- }
+EmulationSession& EmulationSession::GetInstance() {
+ return s_instance;
+}
- const EmuWindow_Android& Window() const {
- return *m_window;
- }
+const Core::System& EmulationSession::System() const {
+ return m_system;
+}
- EmuWindow_Android& Window() {
- return *m_window;
- }
+Core::System& EmulationSession::System() {
+ return m_system;
+}
- ANativeWindow* NativeWindow() const {
- return m_native_window;
- }
+const EmuWindow_Android& EmulationSession::Window() const {
+ return *m_window;
+}
- void SetNativeWindow(ANativeWindow* native_window) {
- m_native_window = native_window;
- }
+EmuWindow_Android& EmulationSession::Window() {
+ return *m_window;
+}
- int InstallFileToNand(std::string filename, std::string file_extension) {
- jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
- std::size_t block_size) {
- if (src == nullptr || dest == nullptr) {
- return false;
- }
- if (!dest->Resize(src->GetSize())) {
- return false;
- }
+ANativeWindow* EmulationSession::NativeWindow() const {
+ return m_native_window;
+}
- using namespace Common::Literals;
- [[maybe_unused]] std::vector<u8> buffer(1_MiB);
+void EmulationSession::SetNativeWindow(ANativeWindow* native_window) {
+ m_native_window = native_window;
+}
- for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
- jconst read = src->Read(buffer.data(), buffer.size(), i);
- dest->Write(buffer.data(), read, i);
- }
- return true;
- };
-
- enum InstallResult {
- Success = 0,
- SuccessFileOverwritten = 1,
- InstallError = 2,
- ErrorBaseGame = 3,
- ErrorFilenameExtension = 4,
- };
-
- m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
- m_system.GetFileSystemController().CreateFactories(*m_vfs);
-
- [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
- if (file_extension == "nsp") {
- nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
- if (nsp->IsExtractedType()) {
- return InstallError;
- }
- } else {
- return ErrorFilenameExtension;
+int EmulationSession::InstallFileToNand(std::string filename, std::string file_extension) {
+ jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
+ std::size_t block_size) {
+ if (src == nullptr || dest == nullptr) {
+ return false;
}
-
- if (!nsp) {
- return InstallError;
+ if (!dest->Resize(src->GetSize())) {
+ return false;
}
- if (nsp->GetStatus() != Loader::ResultStatus::Success) {
- return InstallError;
- }
+ using namespace Common::Literals;
+ [[maybe_unused]] std::vector<u8> buffer(1_MiB);
- jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(
- *nsp, true, copy_func);
-
- switch (res) {
- case FileSys::InstallResult::Success:
- return Success;
- case FileSys::InstallResult::OverwriteExisting:
- return SuccessFileOverwritten;
- case FileSys::InstallResult::ErrorBaseInstall:
- return ErrorBaseGame;
- default:
- return InstallError;
+ for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
+ jconst read = src->Read(buffer.data(), buffer.size(), i);
+ dest->Write(buffer.data(), read, i);
}
- }
+ return true;
+ };
- void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
- const std::string& custom_driver_name,
- const std::string& file_redirect_dir) {
-#ifdef ARCHITECTURE_arm64
- void* handle{};
- const char* file_redirect_dir_{};
- int featureFlags{};
-
- // Enable driver file redirection when renderer debugging is enabled.
- if (Settings::values.renderer_debug && file_redirect_dir.size()) {
- featureFlags |= ADRENOTOOLS_DRIVER_FILE_REDIRECT;
- file_redirect_dir_ = file_redirect_dir.c_str();
- }
+ enum InstallResult {
+ Success = 0,
+ SuccessFileOverwritten = 1,
+ InstallError = 2,
+ ErrorBaseGame = 3,
+ ErrorFilenameExtension = 4,
+ };
- // Try to load a custom driver.
- if (custom_driver_name.size()) {
- handle = adrenotools_open_libvulkan(
- RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(),
- custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr);
- }
+ m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
+ m_system.GetFileSystemController().CreateFactories(*m_vfs);
- // Try to load the system driver.
- if (!handle) {
- handle =
- adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(),
- nullptr, nullptr, file_redirect_dir_, nullptr);
+ [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
+ if (file_extension == "nsp") {
+ nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
+ if (nsp->IsExtractedType()) {
+ return InstallError;
}
-
- m_vulkan_library = std::make_shared<Common::DynamicLibrary>(handle);
-#endif
+ } else {
+ return ErrorFilenameExtension;
}
- bool IsRunning() const {
- return m_is_running;
+ if (!nsp) {
+ return InstallError;
}
- bool IsPaused() const {
- return m_is_running && m_is_paused;
+ if (nsp->GetStatus() != Loader::ResultStatus::Success) {
+ return InstallError;
}
- const Core::PerfStatsResults& PerfStats() const {
- std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
- return m_perf_stats;
- }
+ jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true,
+ copy_func);
- void SurfaceChanged() {
- if (!IsRunning()) {
- return;
- }
- m_window->OnSurfaceChanged(m_native_window);
+ switch (res) {
+ case FileSys::InstallResult::Success:
+ return Success;
+ case FileSys::InstallResult::OverwriteExisting:
+ return SuccessFileOverwritten;
+ case FileSys::InstallResult::ErrorBaseInstall:
+ return ErrorBaseGame;
+ default:
+ return InstallError;
}
+}
- void ConfigureFilesystemProvider(const std::string& filepath) {
- const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read);
- if (!file) {
- return;
- }
-
- auto loader = Loader::GetLoader(m_system, file);
- if (!loader) {
- return;
- }
-
- const auto file_type = loader->GetFileType();
- if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
- return;
- }
+void EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir,
+ const std::string& custom_driver_dir,
+ const std::string& custom_driver_name,
+ const std::string& file_redirect_dir) {
+#ifdef ARCHITECTURE_arm64
+ void* handle{};
+ const char* file_redirect_dir_{};
+ int featureFlags{};
- u64 program_id = 0;
- const auto res2 = loader->ReadProgramId(program_id);
- if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
- m_manual_provider->AddEntry(FileSys::TitleType::Application,
- FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
- program_id, file);
- } else if (res2 == Loader::ResultStatus::Success &&
- (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
- const auto nsp = file_type == Loader::FileType::NSP
- ? std::make_shared<FileSys::NSP>(file)
- : FileSys::XCI{file}.GetSecurePartitionNSP();
- for (const auto& title : nsp->GetNCAs()) {
- for (const auto& entry : title.second) {
- m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
- entry.second->GetBaseFile());
- }
- }
- }
+ // Enable driver file redirection when renderer debugging is enabled.
+ if (Settings::values.renderer_debug && file_redirect_dir.size()) {
+ featureFlags |= ADRENOTOOLS_DRIVER_FILE_REDIRECT;
+ file_redirect_dir_ = file_redirect_dir.c_str();
}
- Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
- std::scoped_lock lock(m_mutex);
-
- // Create the render window.
- m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,
- m_vulkan_library);
-
- m_system.SetFilesystem(m_vfs);
- m_system.GetUserChannel().clear();
-
- // Initialize system.
- jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
- m_software_keyboard = android_keyboard.get();
- m_system.SetShuttingDown(false);
- m_system.ApplySettings();
- Settings::LogSettings();
- m_system.HIDCore().ReloadInputDevices();
- m_system.SetAppletFrontendSet({
- nullptr, // Amiibo Settings
- nullptr, // Controller Selector
- nullptr, // Error Display
- nullptr, // Mii Editor
- nullptr, // Parental Controls
- nullptr, // Photo Viewer
- nullptr, // Profile Selector
- std::move(android_keyboard), // Software Keyboard
- nullptr, // Web Browser
- });
-
- // Initialize filesystem.
- m_manual_provider = std::make_unique<FileSys::ManualContentProvider>();
- m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
- m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
- m_manual_provider.get());
- m_system.GetFileSystemController().CreateFactories(*m_vfs);
- ConfigureFilesystemProvider(filepath);
-
- // Initialize account manager
- m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
-
- // Load the ROM.
- m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
- if (m_load_result != Core::SystemResultStatus::Success) {
- return m_load_result;
- }
-
- // Complete initialization.
- m_system.GPU().Start();
- m_system.GetCpuManager().OnGpuReady();
- m_system.RegisterExitCallback([&] { HaltEmulation(); });
+ // Try to load a custom driver.
+ if (custom_driver_name.size()) {
+ handle = adrenotools_open_libvulkan(
+ RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(),
+ custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr);
+ }
- return Core::SystemResultStatus::Success;
+ // Try to load the system driver.
+ if (!handle) {
+ handle = adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(),
+ nullptr, nullptr, file_redirect_dir_, nullptr);
}
- void ShutdownEmulation() {
- std::scoped_lock lock(m_mutex);
+ m_vulkan_library = std::make_shared<Common::DynamicLibrary>(handle);
+#endif
+}
- m_is_running = false;
+bool EmulationSession::IsRunning() const {
+ return m_is_running;
+}
- // Unload user input.
- m_system.HIDCore().UnloadInputDevices();
+bool EmulationSession::IsPaused() const {
+ return m_is_running && m_is_paused;
+}
- // Shutdown the main emulated process
- if (m_load_result == Core::SystemResultStatus::Success) {
- m_system.DetachDebugger();
- m_system.ShutdownMainProcess();
- m_detached_tasks.WaitForAllTasks();
- m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
- m_window.reset();
- OnEmulationStopped(Core::SystemResultStatus::Success);
- return;
- }
+const Core::PerfStatsResults& EmulationSession::PerfStats() const {
+ std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
+ return m_perf_stats;
+}
- // Tear down the render window.
- m_window.reset();
+void EmulationSession::SurfaceChanged() {
+ if (!IsRunning()) {
+ return;
}
+ m_window->OnSurfaceChanged(m_native_window);
+}
- void PauseEmulation() {
- std::scoped_lock lock(m_mutex);
- m_system.Pause();
- m_is_paused = true;
+void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) {
+ const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read);
+ if (!file) {
+ return;
}
- void UnPauseEmulation() {
- std::scoped_lock lock(m_mutex);
- m_system.Run();
- m_is_paused = false;
+ auto loader = Loader::GetLoader(m_system, file);
+ if (!loader) {
+ return;
}
- void HaltEmulation() {
- std::scoped_lock lock(m_mutex);
- m_is_running = false;
- m_cv.notify_one();
+ const auto file_type = loader->GetFileType();
+ if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
+ return;
}
- void RunEmulation() {
- {
- std::scoped_lock lock(m_mutex);
- m_is_running = true;
- }
-
- // Load the disk shader cache.
- if (Settings::values.use_disk_shader_cache.GetValue()) {
- LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
- m_system.Renderer().ReadRasterizer()->LoadDiskResources(
- m_system.GetApplicationProcessProgramID(), std::stop_token{},
- LoadDiskCacheProgress);
- LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
+ u64 program_id = 0;
+ const auto res2 = loader->ReadProgramId(program_id);
+ if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
+ m_manual_provider->AddEntry(FileSys::TitleType::Application,
+ FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
+ program_id, file);
+ } else if (res2 == Loader::ResultStatus::Success &&
+ (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
+ const auto nsp = file_type == Loader::FileType::NSP
+ ? std::make_shared<FileSys::NSP>(file)
+ : FileSys::XCI{file}.GetSecurePartitionNSP();
+ for (const auto& title : nsp->GetNCAs()) {
+ for (const auto& entry : title.second) {
+ m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
+ entry.second->GetBaseFile());
+ }
}
+ }
+}
- void(m_system.Run());
-
- if (m_system.DebuggerEnabled()) {
- m_system.InitializeDebugger();
- }
+Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath) {
+ std::scoped_lock lock(m_mutex);
+
+ // Create the render window.
+ m_window =
+ std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, m_vulkan_library);
+
+ m_system.SetFilesystem(m_vfs);
+ m_system.GetUserChannel().clear();
+
+ // Initialize system.
+ jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
+ m_software_keyboard = android_keyboard.get();
+ m_system.SetShuttingDown(false);
+ m_system.ApplySettings();
+ Settings::LogSettings();
+ m_system.HIDCore().ReloadInputDevices();
+ m_system.SetAppletFrontendSet({
+ nullptr, // Amiibo Settings
+ nullptr, // Controller Selector
+ nullptr, // Error Display
+ nullptr, // Mii Editor
+ nullptr, // Parental Controls
+ nullptr, // Photo Viewer
+ nullptr, // Profile Selector
+ std::move(android_keyboard), // Software Keyboard
+ nullptr, // Web Browser
+ });
+
+ // Initialize filesystem.
+ m_manual_provider = std::make_unique<FileSys::ManualContentProvider>();
+ m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
+ m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
+ m_manual_provider.get());
+ m_system.GetFileSystemController().CreateFactories(*m_vfs);
+ ConfigureFilesystemProvider(filepath);
+
+ // Initialize account manager
+ m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
+
+ // Load the ROM.
+ m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
+ if (m_load_result != Core::SystemResultStatus::Success) {
+ return m_load_result;
+ }
+
+ // Complete initialization.
+ m_system.GPU().Start();
+ m_system.GetCpuManager().OnGpuReady();
+ m_system.RegisterExitCallback([&] { HaltEmulation(); });
- OnEmulationStarted();
+ return Core::SystemResultStatus::Success;
+}
- while (true) {
- {
- [[maybe_unused]] std::unique_lock lock(m_mutex);
- if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
- [&]() { return !m_is_running; })) {
- // Emulation halted.
- break;
- }
- }
- {
- // Refresh performance stats.
- std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
- m_perf_stats = m_system.GetAndResetPerfStats();
- }
- }
- }
+void EmulationSession::ShutdownEmulation() {
+ std::scoped_lock lock(m_mutex);
- std::string GetRomTitle(const std::string& path) {
- return GetRomMetadata(path).title;
- }
+ m_is_running = false;
- std::vector<u8> GetRomIcon(const std::string& path) {
- return GetRomMetadata(path).icon;
- }
+ // Unload user input.
+ m_system.HIDCore().UnloadInputDevices();
- bool GetIsHomebrew(const std::string& path) {
- return GetRomMetadata(path).isHomebrew;
+ // Shutdown the main emulated process
+ if (m_load_result == Core::SystemResultStatus::Success) {
+ m_system.DetachDebugger();
+ m_system.ShutdownMainProcess();
+ m_detached_tasks.WaitForAllTasks();
+ m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
+ m_window.reset();
+ OnEmulationStopped(Core::SystemResultStatus::Success);
+ return;
}
- void ResetRomMetadata() {
- m_rom_metadata_cache.clear();
- }
+ // Tear down the render window.
+ m_window.reset();
+}
- bool IsHandheldOnly() {
- jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
+void EmulationSession::PauseEmulation() {
+ std::scoped_lock lock(m_mutex);
+ m_system.Pause();
+ m_is_paused = true;
+}
- if (npad_style_set.fullkey == 1) {
- return false;
- }
+void EmulationSession::UnPauseEmulation() {
+ std::scoped_lock lock(m_mutex);
+ m_system.Run();
+ m_is_paused = false;
+}
- if (npad_style_set.handheld == 0) {
- return false;
- }
+void EmulationSession::HaltEmulation() {
+ std::scoped_lock lock(m_mutex);
+ m_is_running = false;
+ m_cv.notify_one();
+}
- return !Settings::IsDockedMode();
+void EmulationSession::RunEmulation() {
+ {
+ std::scoped_lock lock(m_mutex);
+ m_is_running = true;
}
- void SetDeviceType([[maybe_unused]] int index, int type) {
- jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
- controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
+ // Load the disk shader cache.
+ if (Settings::values.use_disk_shader_cache.GetValue()) {
+ LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
+ m_system.Renderer().ReadRasterizer()->LoadDiskResources(
+ m_system.GetApplicationProcessProgramID(), std::stop_token{}, LoadDiskCacheProgress);
+ LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
}
- void OnGamepadConnectEvent([[maybe_unused]] int index) {
- jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
-
- // Ensure that player1 is configured correctly and handheld disconnected
- if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
- jauto handheld =
- m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
+ void(m_system.Run());
- if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
- handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
- controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
- handheld->Disconnect();
- }
- }
+ if (m_system.DebuggerEnabled()) {
+ m_system.InitializeDebugger();
+ }
- // Ensure that handheld is configured correctly and player 1 disconnected
- if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
- jauto player1 =
- m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
+ OnEmulationStarted();
- if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
- player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
- controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
- player1->Disconnect();
+ while (true) {
+ {
+ [[maybe_unused]] std::unique_lock lock(m_mutex);
+ if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
+ [&]() { return !m_is_running; })) {
+ // Emulation halted.
+ break;
}
}
-
- if (!controller->IsConnected()) {
- controller->Connect();
+ {
+ // Refresh performance stats.
+ std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
+ m_perf_stats = m_system.GetAndResetPerfStats();
}
}
+}
+
+bool EmulationSession::IsHandheldOnly() {
+ jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
- void OnGamepadDisconnectEvent([[maybe_unused]] int index) {
- jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
- controller->Disconnect();
+ if (npad_style_set.fullkey == 1) {
+ return false;
}
- SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard() {
- return m_software_keyboard;
+ if (npad_style_set.handheld == 0) {
+ return false;
}
-private:
- struct RomMetadata {
- std::string title;
- std::vector<u8> icon;
- bool isHomebrew;
- };
+ return !Settings::IsDockedMode();
+}
- RomMetadata GetRomMetadata(const std::string& path) {
- if (jauto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
- return search->second;
- }
+void EmulationSession::SetDeviceType([[maybe_unused]] int index, int type) {
+ jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
+ controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
+}
- return CacheRomMetadata(path);
- }
+void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) {
+ jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
- RomMetadata CacheRomMetadata(const std::string& path) {
- jconst file = Core::GetGameFileFromPath(m_vfs, path);
- jauto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
+ // Ensure that player1 is configured correctly and handheld disconnected
+ if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
+ jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
- RomMetadata entry;
- loader->ReadTitle(entry.title);
- loader->ReadIcon(entry.icon);
- if (loader->GetFileType() == Loader::FileType::NRO) {
- jauto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
- entry.isHomebrew = loader_nro->IsHomebrew();
- } else {
- entry.isHomebrew = false;
+ if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
+ handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
+ controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
+ handheld->Disconnect();
}
-
- m_rom_metadata_cache[path] = entry;
-
- return entry;
}
-private:
- static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) {
- JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(),
- IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage),
- static_cast<jint>(progress), static_cast<jint>(max));
- }
+ // Ensure that handheld is configured correctly and player 1 disconnected
+ if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
+ jauto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
- static void OnEmulationStarted() {
- JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
- IDCache::GetOnEmulationStarted());
+ if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
+ player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
+ controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
+ player1->Disconnect();
+ }
}
- static void OnEmulationStopped(Core::SystemResultStatus result) {
- JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
- IDCache::GetOnEmulationStopped(), static_cast<jint>(result));
+ if (!controller->IsConnected()) {
+ controller->Connect();
}
+}
-private:
- static EmulationSession s_instance;
-
- // Frontend management
- std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache;
-
- // Window management
- std::unique_ptr<EmuWindow_Android> m_window;
- ANativeWindow* m_native_window{};
-
- // Core emulation
- Core::System m_system;
- InputCommon::InputSubsystem m_input_subsystem;
- Common::DetachedTasks m_detached_tasks;
- Core::PerfStatsResults m_perf_stats{};
- std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
- Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
- std::atomic<bool> m_is_running = false;
- std::atomic<bool> m_is_paused = false;
- SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
- std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
- std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
+void EmulationSession::OnGamepadDisconnectEvent([[maybe_unused]] int index) {
+ jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
+ controller->Disconnect();
+}
- // GPU driver parameters
- std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
+SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() {
+ return m_software_keyboard;
+}
- // Synchronization
- std::condition_variable_any m_cv;
- mutable std::mutex m_perf_stats_mutex;
- mutable std::mutex m_mutex;
-};
+void EmulationSession::LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress,
+ int max) {
+ JNIEnv* env = IDCache::GetEnvForThread();
+ env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(),
+ IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage),
+ static_cast<jint>(progress), static_cast<jint>(max));
+}
-/*static*/ EmulationSession EmulationSession::s_instance;
+void EmulationSession::OnEmulationStarted() {
+ JNIEnv* env = IDCache::GetEnvForThread();
+ env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnEmulationStarted());
+}
-} // Anonymous namespace
+void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) {
+ JNIEnv* env = IDCache::GetEnvForThread();
+ env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnEmulationStopped(),
+ static_cast<jint>(result));
+}
static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
Common::Log::Initialize();
@@ -657,10 +558,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation(JNIEnv* env, jclass cla
EmulationSession::GetInstance().HaltEmulation();
}
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata(JNIEnv* env, jclass clazz) {
- EmulationSession::GetInstance().ResetRomMetadata();
-}
-
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) {
return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
}
@@ -766,46 +663,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass c
}
}
-jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass clazz,
- jstring j_filename) {
- jauto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
- jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
- env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
- reinterpret_cast<jbyte*>(icon_data.data()));
- return icon;
-}
-
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle(JNIEnv* env, jclass clazz,
- jstring j_filename) {
- jauto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
- return env->NewStringUTF(title.c_str());
-}
-
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription(JNIEnv* env, jclass clazz,
- jstring j_filename) {
- return j_filename;
-}
-
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId(JNIEnv* env, jclass clazz,
- jstring j_filename) {
- return j_filename;
-}
-
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions(JNIEnv* env, jclass clazz,
- jstring j_filename) {
- return env->NewStringUTF("");
-}
-
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany(JNIEnv* env, jclass clazz,
- jstring j_filename) {
- return env->NewStringUTF("");
-}
-
-jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew(JNIEnv* env, jclass clazz,
- jstring j_filename) {
- return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
-}
-
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) {
// Create the default config.ini.
Config{};
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h
new file mode 100644
index 000000000..b1db87e41
--- /dev/null
+++ b/src/android/app/src/main/jni/native.h
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <android/native_window_jni.h>
+#include "common/detached_tasks.h"
+#include "core/core.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/hle/service/acc/profile_manager.h"
+#include "core/perf_stats.h"
+#include "jni/applets/software_keyboard.h"
+#include "jni/emu_window/emu_window.h"
+#include "video_core/rasterizer_interface.h"
+
+#pragma once
+
+class EmulationSession final {
+public:
+ explicit EmulationSession();
+ ~EmulationSession() = default;
+
+ static EmulationSession& GetInstance();
+ const Core::System& System() const;
+ Core::System& System();
+
+ const EmuWindow_Android& Window() const;
+ EmuWindow_Android& Window();
+ ANativeWindow* NativeWindow() const;
+ void SetNativeWindow(ANativeWindow* native_window);
+ void SurfaceChanged();
+
+ int InstallFileToNand(std::string filename, std::string file_extension);
+ void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
+ const std::string& custom_driver_name,
+ const std::string& file_redirect_dir);
+
+ bool IsRunning() const;
+ bool IsPaused() const;
+ void PauseEmulation();
+ void UnPauseEmulation();
+ void HaltEmulation();
+ void RunEmulation();
+ void ShutdownEmulation();
+
+ const Core::PerfStatsResults& PerfStats() const;
+ void ConfigureFilesystemProvider(const std::string& filepath);
+ Core::SystemResultStatus InitializeEmulation(const std::string& filepath);
+
+ bool IsHandheldOnly();
+ void SetDeviceType([[maybe_unused]] int index, int type);
+ void OnGamepadConnectEvent([[maybe_unused]] int index);
+ void OnGamepadDisconnectEvent([[maybe_unused]] int index);
+ SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard();
+
+private:
+ static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
+ static void OnEmulationStarted();
+ static void OnEmulationStopped(Core::SystemResultStatus result);
+
+private:
+ // Window management
+ std::unique_ptr<EmuWindow_Android> m_window;
+ ANativeWindow* m_native_window{};
+
+ // Core emulation
+ Core::System m_system;
+ InputCommon::InputSubsystem m_input_subsystem;
+ Common::DetachedTasks m_detached_tasks;
+ Core::PerfStatsResults m_perf_stats{};
+ std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
+ Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
+ std::atomic<bool> m_is_running = false;
+ std::atomic<bool> m_is_paused = false;
+ SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
+ std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
+ std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
+
+ // GPU driver parameters
+ std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
+
+ // Synchronization
+ std::condition_variable_any m_cv;
+ mutable std::mutex m_perf_stats_mutex;
+ mutable std::mutex m_mutex;
+};
diff --git a/src/android/app/src/main/res/layout/card_game.xml b/src/android/app/src/main/res/layout/card_game.xml
index 1f5de219b..6340171ec 100644
--- a/src/android/app/src/main/res/layout/card_game.xml
+++ b/src/android/app/src/main/res/layout/card_game.xml
@@ -1,63 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
- style="?attr/materialCardViewElevatedStyle"
android:id="@+id/card_game"
+ style="?attr/materialCardViewElevatedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_gravity="center"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:clipToPadding="false"
android:focusable="true"
android:transitionName="card_game"
- android:layout_gravity="center"
- app:cardElevation="0dp"
- app:cardCornerRadius="12dp">
+ app:cardCornerRadius="4dp"
+ app:cardElevation="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="6dp">
- <com.google.android.material.card.MaterialCardView
- style="?attr/materialCardViewElevatedStyle"
- android:id="@+id/card_game_art"
+ <com.google.android.material.imageview.ShapeableImageView
+ android:id="@+id/image_game_screen"
android:layout_width="150dp"
android:layout_height="150dp"
- app:cardCornerRadius="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent">
-
- <ImageView
- android:id="@+id/image_game_screen"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:src="@drawable/default_icon" />
-
- </com.google.android.material.card.MaterialCardView>
+ app:layout_constraintTop_toTopOf="parent"
+ app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.ExtraSmall"
+ tools:src="@drawable/default_icon" />
<com.google.android.material.textview.MaterialTextView
- style="@style/TextAppearance.Material3.TitleMedium"
android:id="@+id/text_game_title"
+ style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
- android:textAlignment="center"
- android:textSize="14sp"
- android:singleLine="true"
- android:marqueeRepeatLimit="marquee_forever"
android:ellipsize="none"
+ android:marqueeRepeatLimit="marquee_forever"
android:requiresFadingEdge="horizontal"
- app:layout_constraintEnd_toEndOf="@+id/card_game_art"
- app:layout_constraintStart_toStartOf="@+id/card_game_art"
- app:layout_constraintTop_toBottomOf="@+id/card_game_art"
+ android:singleLine="true"
+ android:textAlignment="center"
+ android:textSize="14sp"
+ app:layout_constraintEnd_toEndOf="@+id/image_game_screen"
+ app:layout_constraintStart_toStartOf="@+id/image_game_screen"
+ app:layout_constraintTop_toBottomOf="@+id/image_game_screen"
tools:text="The Legend of Zelda: Skyward Sword" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout/card_home_option.xml b/src/android/app/src/main/res/layout/card_home_option.xml
index f9f1d89fb..6e8a232f9 100644
--- a/src/android/app/src/main/res/layout/card_home_option.xml
+++ b/src/android/app/src/main/res/layout/card_home_option.xml
@@ -16,7 +16,8 @@
<LinearLayout
android:id="@+id/option_layout"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical">
<ImageView
android:id="@+id/option_icon"
diff --git a/src/common/fs/fs_android.cpp b/src/common/fs/fs_android.cpp
index 298a79bac..1dd826a4a 100644
--- a/src/common/fs/fs_android.cpp
+++ b/src/common/fs/fs_android.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/fs/fs_android.h"
+#include "common/string_util.h"
namespace Common::FS::Android {
@@ -28,28 +29,35 @@ void RegisterCallbacks(JNIEnv* env, jclass clazz) {
env->GetJavaVM(&g_jvm);
native_library = clazz;
+#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \
+ F(JMethodID, JMethodName, Signature)
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \
F(JMethodID, JMethodName, Signature)
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \
F(JMethodID, JMethodName, Signature)
#define F(JMethodID, JMethodName, Signature) \
JMethodID = env->GetStaticMethodID(native_library, JMethodName, Signature);
+ ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
ANDROID_STORAGE_FUNCTIONS(FS)
#undef F
#undef FS
#undef FR
+#undef FH
}
void UnRegisterCallbacks() {
+#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID)
#define F(JMethodID) JMethodID = nullptr;
+ ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
ANDROID_STORAGE_FUNCTIONS(FS)
#undef F
#undef FS
#undef FR
+#undef FH
}
bool IsContentUri(const std::string& path) {
@@ -95,4 +103,29 @@ ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
#undef F
#undef FR
+#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \
+ F(FunctionName, JMethodID, Caller)
+#define F(FunctionName, JMethodID, Caller) \
+ std::string FunctionName(const std::string& filepath) { \
+ if (JMethodID == nullptr) { \
+ return 0; \
+ } \
+ auto env = GetEnvForThread(); \
+ jstring j_filepath = env->NewStringUTF(filepath.c_str()); \
+ jstring j_return = \
+ static_cast<jstring>(env->Caller(native_library, JMethodID, j_filepath)); \
+ if (!j_return) { \
+ return {}; \
+ } \
+ const jchar* jchars = env->GetStringChars(j_return, nullptr); \
+ const jsize length = env->GetStringLength(j_return); \
+ const std::u16string_view string_view(reinterpret_cast<const char16_t*>(jchars), length); \
+ const std::string converted_string = Common::UTF16ToUTF8(string_view); \
+ env->ReleaseStringChars(j_return, jchars); \
+ return converted_string; \
+ }
+ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
+#undef F
+#undef FH
+
} // namespace Common::FS::Android
diff --git a/src/common/fs/fs_android.h b/src/common/fs/fs_android.h
index b441c2a12..2c9234313 100644
--- a/src/common/fs/fs_android.h
+++ b/src/common/fs/fs_android.h
@@ -17,19 +17,28 @@
"(Ljava/lang/String;)Z") \
V(Exists, bool, file_exists, CallStaticBooleanMethod, "exists", "(Ljava/lang/String;)Z")
+#define ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(V) \
+ V(GetParentDirectory, get_parent_directory, CallStaticObjectMethod, "getParentDirectory", \
+ "(Ljava/lang/String;)Ljava/lang/String;") \
+ V(GetFilename, get_filename, CallStaticObjectMethod, "getFilename", \
+ "(Ljava/lang/String;)Ljava/lang/String;")
+
namespace Common::FS::Android {
static JavaVM* g_jvm = nullptr;
static jclass native_library = nullptr;
+#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID)
#define F(JMethodID) static jmethodID JMethodID = nullptr;
+ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
ANDROID_STORAGE_FUNCTIONS(FS)
#undef F
#undef FS
#undef FR
+#undef FH
enum class OpenMode {
Read,
@@ -62,4 +71,10 @@ ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
#undef F
#undef FR
+#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(FunctionName)
+#define F(FunctionName) std::string FunctionName(const std::string& filepath);
+ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
+#undef F
+#undef FH
+
} // namespace Common::FS::Android
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h
index 441c8af97..bcf447089 100644
--- a/src/common/fs/fs_paths.h
+++ b/src/common/fs/fs_paths.h
@@ -13,6 +13,7 @@
#define AMIIBO_DIR "amiibo"
#define CACHE_DIR "cache"
#define CONFIG_DIR "config"
+#define CRASH_DUMPS_DIR "crash_dumps"
#define DUMP_DIR "dump"
#define KEYS_DIR "keys"
#define LOAD_DIR "load"
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index 0abd81a45..c3a81f9a9 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -119,6 +119,7 @@ public:
GenerateYuzuPath(YuzuPath::AmiiboDir, yuzu_path / AMIIBO_DIR);
GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache);
GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config);
+ GenerateYuzuPath(YuzuPath::CrashDumpsDir, yuzu_path / CRASH_DUMPS_DIR);
GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR);
GenerateYuzuPath(YuzuPath::KeysDir, yuzu_path / KEYS_DIR);
GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
@@ -400,6 +401,16 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se
}
std::string_view GetParentPath(std::string_view path) {
+ if (path.empty()) {
+ return path;
+ }
+
+#ifdef ANDROID
+ if (path[0] != '/') {
+ std::string path_string{path};
+ return FS::Android::GetParentDirectory(path_string);
+ }
+#endif
const auto name_bck_index = path.rfind('\\');
const auto name_fwd_index = path.rfind('/');
std::size_t name_index;
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index 63801c924..2874ea738 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -15,6 +15,7 @@ enum class YuzuPath {
AmiiboDir, // Where Amiibo backups are stored.
CacheDir, // Where cached filesystem data is stored.
ConfigDir, // Where config files are stored.
+ CrashDumpsDir, // Where crash dumps are stored.
DumpDir, // Where dumped data is stored.
KeysDir, // Where key files are stored.
LoadDir, // Where cheat/mod files are stored.
diff --git a/src/common/nvidia_flags.cpp b/src/common/nvidia_flags.cpp
index 7ed7690ee..fa3747782 100644
--- a/src/common/nvidia_flags.cpp
+++ b/src/common/nvidia_flags.cpp
@@ -25,6 +25,7 @@ void ConfigureNvidiaEnvironmentFlags() {
void(_putenv(fmt::format("__GL_SHADER_DISK_CACHE_PATH={}", windows_path_string).c_str()));
void(_putenv("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1"));
+ void(_putenv("__GL_THREADED_OPTIMIZATIONS=1"));
#endif
}
diff --git a/src/common/settings.h b/src/common/settings.h
index 236e33bee..9317075f7 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -505,7 +505,6 @@ struct Values {
linkage, false, "use_auto_stub", Category::Debugging, Specialization::Default, false};
Setting<bool> enable_all_controllers{linkage, false, "enable_all_controllers",
Category::Debugging};
- Setting<bool> create_crash_dumps{linkage, false, "create_crash_dumps", Category::Debugging};
Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging};
// Miscellaneous
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index 4c7aba3f5..72c481798 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -14,6 +14,10 @@
#include <windows.h>
#endif
+#ifdef ANDROID
+#include <common/fs/fs_android.h>
+#endif
+
namespace Common {
/// Make a string lowercase
@@ -63,6 +67,14 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
if (full_path.empty())
return false;
+#ifdef ANDROID
+ if (full_path[0] != '/') {
+ *_pPath = Common::FS::Android::GetParentDirectory(full_path);
+ *_pFilename = Common::FS::Android::GetFilename(full_path);
+ return true;
+ }
+#endif
+
std::size_t dir_end = full_path.find_last_of("/"
// windows needs the : included for something like just "C:" to be considered a directory
#ifdef _WIN32
diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp
index 0c012f094..5e27dde58 100644
--- a/src/core/arm/arm_interface.cpp
+++ b/src/core/arm/arm_interface.cpp
@@ -86,9 +86,9 @@ void ARM_Interface::SymbolicateBacktrace(Core::System& system, std::vector<Backt
std::map<std::string, Symbols::Symbols> symbols;
for (const auto& module : modules) {
- symbols.insert_or_assign(
- module.second, Symbols::GetSymbols(module.first, system.ApplicationMemory(),
- system.ApplicationProcess()->Is64BitProcess()));
+ symbols.insert_or_assign(module.second,
+ Symbols::GetSymbols(module.first, system.ApplicationMemory(),
+ system.ApplicationProcess()->Is64Bit()));
}
for (auto& entry : out) {
diff --git a/src/core/core.cpp b/src/core/core.cpp
index d7e2efbd7..14d6c8c27 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -309,17 +309,10 @@ struct System::Impl {
telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider);
- // Create a resource limit for the process.
- const auto physical_memory_size =
- kernel.MemoryManager().GetSize(Kernel::KMemoryManager::Pool::Application);
- auto* resource_limit = Kernel::CreateResourceLimitForProcess(system, physical_memory_size);
-
// Create the process.
auto main_process = Kernel::KProcess::Create(system.Kernel());
- ASSERT(Kernel::KProcess::Initialize(main_process, system, "main",
- Kernel::KProcess::ProcessType::Userland, resource_limit)
- .IsSuccess());
Kernel::KProcess::Register(system.Kernel(), main_process);
+ kernel.AppendNewProcess(main_process);
kernel.MakeApplicationProcess(main_process);
const auto [load_result, load_parameters] = app_loader->Load(*main_process, system);
if (load_result != Loader::ResultStatus::Success) {
@@ -418,6 +411,7 @@ struct System::Impl {
services->KillNVNFlinger();
}
kernel.CloseServices();
+ kernel.ShutdownCores();
services.reset();
service_manager.reset();
cheat_engine.reset();
@@ -429,7 +423,6 @@ struct System::Impl {
gpu_core.reset();
host1x_core.reset();
perf_stats.reset();
- kernel.ShutdownCores();
cpu_manager.Shutdown();
debugger.reset();
kernel.Shutdown();
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp
index a1589fecb..0e270eb50 100644
--- a/src/core/debugger/debugger.cpp
+++ b/src/core/debugger/debugger.cpp
@@ -258,20 +258,20 @@ private:
Kernel::KScopedSchedulerLock sl{system.Kernel()};
// Put all threads to sleep on next scheduler round.
- for (auto* thread : ThreadList()) {
- thread->RequestSuspend(Kernel::SuspendType::Debug);
+ for (auto& thread : ThreadList()) {
+ thread.RequestSuspend(Kernel::SuspendType::Debug);
}
}
void ResumeEmulation(Kernel::KThread* except = nullptr) {
// Wake up all threads.
- for (auto* thread : ThreadList()) {
- if (thread == except) {
+ for (auto& thread : ThreadList()) {
+ if (std::addressof(thread) == except) {
continue;
}
- thread->SetStepState(Kernel::StepState::NotStepping);
- thread->Resume(Kernel::SuspendType::Debug);
+ thread.SetStepState(Kernel::StepState::NotStepping);
+ thread.Resume(Kernel::SuspendType::Debug);
}
}
@@ -283,13 +283,17 @@ private:
}
void UpdateActiveThread() {
- const auto& threads{ThreadList()};
- if (std::find(threads.begin(), threads.end(), state->active_thread) == threads.end()) {
- state->active_thread = threads.front();
+ auto& threads{ThreadList()};
+ for (auto& thread : threads) {
+ if (std::addressof(thread) == state->active_thread) {
+ // Thread is still alive, no need to update.
+ return;
+ }
}
+ state->active_thread = std::addressof(threads.front());
}
- const std::list<Kernel::KThread*>& ThreadList() {
+ Kernel::KProcess::ThreadList& ThreadList() {
return system.ApplicationProcess()->GetThreadList();
}
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
index 2076aa8a2..6f5f5156b 100644
--- a/src/core/debugger/gdbstub.cpp
+++ b/src/core/debugger/gdbstub.cpp
@@ -109,7 +109,7 @@ static std::string EscapeXML(std::string_view data) {
GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_)
: DebuggerFrontend(backend_), system{system_} {
- if (system.ApplicationProcess()->Is64BitProcess()) {
+ if (system.ApplicationProcess()->Is64Bit()) {
arch = std::make_unique<GDBStubA64>();
} else {
arch = std::make_unique<GDBStubA32>();
@@ -446,10 +446,10 @@ void GDBStub::HandleBreakpointRemove(std::string_view command) {
// See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp
static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory& memory,
- const Kernel::KThread* thread) {
+ const Kernel::KThread& thread) {
// Read thread type from TLS
- const VAddr tls_thread_type{memory.Read32(thread->GetTlsAddress() + 0x1fc)};
- const VAddr argument_thread_type{thread->GetArgument()};
+ const VAddr tls_thread_type{memory.Read32(thread.GetTlsAddress() + 0x1fc)};
+ const VAddr argument_thread_type{thread.GetArgument()};
if (argument_thread_type && tls_thread_type != argument_thread_type) {
// Probably not created by nnsdk, no name available.
@@ -477,10 +477,10 @@ static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory&
}
static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory& memory,
- const Kernel::KThread* thread) {
+ const Kernel::KThread& thread) {
// Read thread type from TLS
- const VAddr tls_thread_type{memory.Read64(thread->GetTlsAddress() + 0x1f8)};
- const VAddr argument_thread_type{thread->GetArgument()};
+ const VAddr tls_thread_type{memory.Read64(thread.GetTlsAddress() + 0x1f8)};
+ const VAddr argument_thread_type{thread.GetArgument()};
if (argument_thread_type && tls_thread_type != argument_thread_type) {
// Probably not created by nnsdk, no name available.
@@ -508,16 +508,16 @@ static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory&
}
static std::optional<std::string> GetThreadName(Core::System& system,
- const Kernel::KThread* thread) {
- if (system.ApplicationProcess()->Is64BitProcess()) {
+ const Kernel::KThread& thread) {
+ if (system.ApplicationProcess()->Is64Bit()) {
return GetNameFromThreadType64(system.ApplicationMemory(), thread);
} else {
return GetNameFromThreadType32(system.ApplicationMemory(), thread);
}
}
-static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) {
- switch (thread->GetWaitReasonForDebugging()) {
+static std::string_view GetThreadWaitReason(const Kernel::KThread& thread) {
+ switch (thread.GetWaitReasonForDebugging()) {
case Kernel::ThreadWaitReasonForDebugging::Sleep:
return "Sleep";
case Kernel::ThreadWaitReasonForDebugging::IPC:
@@ -535,8 +535,8 @@ static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) {
}
}
-static std::string GetThreadState(const Kernel::KThread* thread) {
- switch (thread->GetState()) {
+static std::string GetThreadState(const Kernel::KThread& thread) {
+ switch (thread.GetState()) {
case Kernel::ThreadState::Initialized:
return "Initialized";
case Kernel::ThreadState::Waiting:
@@ -604,7 +604,7 @@ void GDBStub::HandleQuery(std::string_view command) {
const auto& threads = system.ApplicationProcess()->GetThreadList();
std::vector<std::string> thread_ids;
for (const auto& thread : threads) {
- thread_ids.push_back(fmt::format("{:x}", thread->GetThreadId()));
+ thread_ids.push_back(fmt::format("{:x}", thread.GetThreadId()));
}
SendReply(fmt::format("m{}", fmt::join(thread_ids, ",")));
} else if (command.starts_with("sThreadInfo")) {
@@ -616,14 +616,14 @@ void GDBStub::HandleQuery(std::string_view command) {
buffer += "<threads>";
const auto& threads = system.ApplicationProcess()->GetThreadList();
- for (const auto* thread : threads) {
+ for (const auto& thread : threads) {
auto thread_name{GetThreadName(system, thread)};
if (!thread_name) {
- thread_name = fmt::format("Thread {:d}", thread->GetThreadId());
+ thread_name = fmt::format("Thread {:d}", thread.GetThreadId());
}
buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="{}">{}</thread>)",
- thread->GetThreadId(), thread->GetActiveCore(),
+ thread.GetThreadId(), thread.GetActiveCore(),
EscapeXML(*thread_name), GetThreadState(thread));
}
@@ -850,10 +850,10 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
}
Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
- const auto& threads{system.ApplicationProcess()->GetThreadList()};
- for (auto* thread : threads) {
- if (thread->GetThreadId() == thread_id) {
- return thread;
+ auto& threads{system.ApplicationProcess()->GetThreadList()};
+ for (auto& thread : threads) {
+ if (thread.GetThreadId() == thread_id) {
+ return std::addressof(thread);
}
}
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index 8e291ff67..763a44fee 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -104,16 +104,16 @@ Loader::ResultStatus ProgramMetadata::Reload(VirtualFile file) {
}
/*static*/ ProgramMetadata ProgramMetadata::GetDefault() {
- // Allow use of cores 0~3 and thread priorities 1~63.
- constexpr u32 default_thread_info_capability = 0x30007F7;
+ // Allow use of cores 0~3 and thread priorities 16~63.
+ constexpr u32 default_thread_info_capability = 0x30043F7;
ProgramMetadata result;
result.LoadManual(
true /*is_64_bit*/, FileSys::ProgramAddressSpaceType::Is39Bit /*address_space*/,
- 0x2c /*main_thread_prio*/, 0 /*main_thread_core*/, 0x00100000 /*main_thread_stack_size*/,
- 0 /*title_id*/, 0xFFFFFFFFFFFFFFFF /*filesystem_permissions*/,
- 0x1FE00000 /*system_resource_size*/, {default_thread_info_capability} /*capabilities*/);
+ 0x2c /*main_thread_prio*/, 0 /*main_thread_core*/, 0x100000 /*main_thread_stack_size*/,
+ 0 /*title_id*/, 0xFFFFFFFFFFFFFFFF /*filesystem_permissions*/, 0 /*system_resource_size*/,
+ {default_thread_info_capability} /*capabilities*/);
return result;
}
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index 9f8e74b13..76ee97d78 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -73,6 +73,9 @@ public:
u64 GetFilesystemPermissions() const;
u32 GetSystemResourceSize() const;
const KernelCapabilityDescriptors& GetKernelCapabilities() const;
+ const std::array<u8, 0x10>& GetName() const {
+ return npdm_header.application_name;
+ }
void Print() const;
@@ -164,14 +167,14 @@ private:
u32_le unk_size_2;
};
- Header npdm_header;
- AciHeader aci_header;
- AcidHeader acid_header;
+ Header npdm_header{};
+ AciHeader aci_header{};
+ AcidHeader acid_header{};
- FileAccessControl acid_file_access;
- FileAccessHeader aci_file_access;
+ FileAccessControl acid_file_access{};
+ FileAccessHeader aci_file_access{};
- KernelCapabilityDescriptors aci_kernel_capabilities;
+ KernelCapabilityDescriptors aci_kernel_capabilities{};
};
} // namespace FileSys
diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
index 4cfdf4558..59364efa1 100644
--- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
+++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
@@ -8,7 +8,11 @@
#include "core/hle/kernel/board/nintendo/nx/k_system_control.h"
#include "core/hle/kernel/board/nintendo/nx/secure_monitor.h"
+#include "core/hle/kernel/k_memory_manager.h"
+#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_trace.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/svc_results.h"
namespace Kernel::Board::Nintendo::Nx {
@@ -30,6 +34,8 @@ constexpr const std::size_t RequiredNonSecureSystemMemorySize =
constexpr const std::size_t RequiredNonSecureSystemMemorySizeWithFatal =
RequiredNonSecureSystemMemorySize + impl::RequiredNonSecureSystemMemorySizeViFatal;
+constexpr const std::size_t SecureAlignment = 128_KiB;
+
namespace {
using namespace Common::Literals;
@@ -183,4 +189,57 @@ u64 KSystemControl::GenerateRandomRange(u64 min, u64 max) {
return GenerateUniformRange(min, max, GenerateRandomU64);
}
+size_t KSystemControl::CalculateRequiredSecureMemorySize(size_t size, u32 pool) {
+ if (pool == static_cast<u32>(KMemoryManager::Pool::Applet)) {
+ return 0;
+ } else {
+ // return KSystemControlBase::CalculateRequiredSecureMemorySize(size, pool);
+ return size;
+ }
+}
+
+Result KSystemControl::AllocateSecureMemory(KernelCore& kernel, KVirtualAddress* out, size_t size,
+ u32 pool) {
+ // Applet secure memory is handled separately.
+ UNIMPLEMENTED_IF(pool == static_cast<u32>(KMemoryManager::Pool::Applet));
+
+ // Ensure the size is aligned.
+ const size_t alignment =
+ (pool == static_cast<u32>(KMemoryManager::Pool::System) ? PageSize : SecureAlignment);
+ R_UNLESS(Common::IsAligned(size, alignment), ResultInvalidSize);
+
+ // Allocate the memory.
+ const size_t num_pages = size / PageSize;
+ const KPhysicalAddress paddr = kernel.MemoryManager().AllocateAndOpenContinuous(
+ num_pages, alignment / PageSize,
+ KMemoryManager::EncodeOption(static_cast<KMemoryManager::Pool>(pool),
+ KMemoryManager::Direction::FromFront));
+ R_UNLESS(paddr != 0, ResultOutOfMemory);
+
+ // Ensure we don't leak references to the memory on error.
+ ON_RESULT_FAILURE {
+ kernel.MemoryManager().Close(paddr, num_pages);
+ };
+
+ // We succeeded.
+ *out = KPageTable::GetHeapVirtualAddress(kernel.MemoryLayout(), paddr);
+ R_SUCCEED();
+}
+
+void KSystemControl::FreeSecureMemory(KernelCore& kernel, KVirtualAddress address, size_t size,
+ u32 pool) {
+ // Applet secure memory is handled separately.
+ UNIMPLEMENTED_IF(pool == static_cast<u32>(KMemoryManager::Pool::Applet));
+
+ // Ensure the size is aligned.
+ const size_t alignment =
+ (pool == static_cast<u32>(KMemoryManager::Pool::System) ? PageSize : SecureAlignment);
+ ASSERT(Common::IsAligned(GetInteger(address), alignment));
+ ASSERT(Common::IsAligned(size, alignment));
+
+ // Close the secure region's pages.
+ kernel.MemoryManager().Close(KPageTable::GetHeapPhysicalAddress(kernel.MemoryLayout(), address),
+ size / PageSize);
+}
+
} // namespace Kernel::Board::Nintendo::Nx
diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.h b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
index b477e8193..ff1feec70 100644
--- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
+++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
@@ -4,6 +4,11 @@
#pragma once
#include "core/hle/kernel/k_typed_address.h"
+#include "core/hle/result.h"
+
+namespace Kernel {
+class KernelCore;
+}
namespace Kernel::Board::Nintendo::Nx {
@@ -25,8 +30,16 @@ public:
static std::size_t GetMinimumNonSecureSystemPoolSize();
};
+ // Randomness.
static u64 GenerateRandomRange(u64 min, u64 max);
static u64 GenerateRandomU64();
+
+ // Secure Memory.
+ static size_t CalculateRequiredSecureMemorySize(size_t size, u32 pool);
+ static Result AllocateSecureMemory(KernelCore& kernel, KVirtualAddress* out, size_t size,
+ u32 pool);
+ static void FreeSecureMemory(KernelCore& kernel, KVirtualAddress address, size_t size,
+ u32 pool);
};
} // namespace Kernel::Board::Nintendo::Nx
diff --git a/src/core/hle/kernel/k_capabilities.h b/src/core/hle/kernel/k_capabilities.h
index de766c811..ebd4eedb1 100644
--- a/src/core/hle/kernel/k_capabilities.h
+++ b/src/core/hle/kernel/k_capabilities.h
@@ -200,8 +200,8 @@ private:
RawCapabilityValue raw;
BitField<0, 15, CapabilityType> id;
- BitField<15, 4, u32> major_version;
- BitField<19, 13, u32> minor_version;
+ BitField<15, 4, u32> minor_version;
+ BitField<19, 13, u32> major_version;
};
union HandleTable {
diff --git a/src/core/hle/kernel/k_condition_variable.cpp b/src/core/hle/kernel/k_condition_variable.cpp
index efbac0e6a..7633a51fb 100644
--- a/src/core/hle/kernel/k_condition_variable.cpp
+++ b/src/core/hle/kernel/k_condition_variable.cpp
@@ -107,12 +107,12 @@ KConditionVariable::KConditionVariable(Core::System& system)
KConditionVariable::~KConditionVariable() = default;
-Result KConditionVariable::SignalToAddress(KProcessAddress addr) {
- KThread* owner_thread = GetCurrentThreadPointer(m_kernel);
+Result KConditionVariable::SignalToAddress(KernelCore& kernel, KProcessAddress addr) {
+ KThread* owner_thread = GetCurrentThreadPointer(kernel);
// Signal the address.
{
- KScopedSchedulerLock sl(m_kernel);
+ KScopedSchedulerLock sl(kernel);
// Remove waiter thread.
bool has_waiters{};
@@ -133,7 +133,7 @@ Result KConditionVariable::SignalToAddress(KProcessAddress addr) {
// Write the value to userspace.
Result result{ResultSuccess};
- if (WriteToUser(m_kernel, addr, std::addressof(next_value))) [[likely]] {
+ if (WriteToUser(kernel, addr, std::addressof(next_value))) [[likely]] {
result = ResultSuccess;
} else {
result = ResultInvalidCurrentMemory;
@@ -148,28 +148,28 @@ Result KConditionVariable::SignalToAddress(KProcessAddress addr) {
}
}
-Result KConditionVariable::WaitForAddress(Handle handle, KProcessAddress addr, u32 value) {
- KThread* cur_thread = GetCurrentThreadPointer(m_kernel);
- ThreadQueueImplForKConditionVariableWaitForAddress wait_queue(m_kernel);
+Result KConditionVariable::WaitForAddress(KernelCore& kernel, Handle handle, KProcessAddress addr,
+ u32 value) {
+ KThread* cur_thread = GetCurrentThreadPointer(kernel);
+ ThreadQueueImplForKConditionVariableWaitForAddress wait_queue(kernel);
// Wait for the address.
KThread* owner_thread{};
{
- KScopedSchedulerLock sl(m_kernel);
+ KScopedSchedulerLock sl(kernel);
// Check if the thread should terminate.
R_UNLESS(!cur_thread->IsTerminationRequested(), ResultTerminationRequested);
// Read the tag from userspace.
u32 test_tag{};
- R_UNLESS(ReadFromUser(m_kernel, std::addressof(test_tag), addr),
- ResultInvalidCurrentMemory);
+ R_UNLESS(ReadFromUser(kernel, std::addressof(test_tag), addr), ResultInvalidCurrentMemory);
// If the tag isn't the handle (with wait mask), we're done.
R_SUCCEED_IF(test_tag != (handle | Svc::HandleWaitMask));
// Get the lock owner thread.
- owner_thread = GetCurrentProcess(m_kernel)
+ owner_thread = GetCurrentProcess(kernel)
.GetHandleTable()
.GetObjectWithoutPseudoHandle<KThread>(handle)
.ReleasePointerUnsafe();
diff --git a/src/core/hle/kernel/k_condition_variable.h b/src/core/hle/kernel/k_condition_variable.h
index 8c2f3ae51..2620c8e39 100644
--- a/src/core/hle/kernel/k_condition_variable.h
+++ b/src/core/hle/kernel/k_condition_variable.h
@@ -24,11 +24,12 @@ public:
explicit KConditionVariable(Core::System& system);
~KConditionVariable();
- // Arbitration
- Result SignalToAddress(KProcessAddress addr);
- Result WaitForAddress(Handle handle, KProcessAddress addr, u32 value);
+ // Arbitration.
+ static Result SignalToAddress(KernelCore& kernel, KProcessAddress addr);
+ static Result WaitForAddress(KernelCore& kernel, Handle handle, KProcessAddress addr,
+ u32 value);
- // Condition variable
+ // Condition variable.
void Signal(u64 cv_key, s32 count);
Result Wait(KProcessAddress addr, u64 key, u32 value, s64 timeout);
diff --git a/src/core/hle/kernel/k_interrupt_manager.cpp b/src/core/hle/kernel/k_interrupt_manager.cpp
index fe6a20168..22d79569a 100644
--- a/src/core/hle/kernel/k_interrupt_manager.cpp
+++ b/src/core/hle/kernel/k_interrupt_manager.cpp
@@ -22,7 +22,7 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) {
KScopedSchedulerLock sl{kernel};
// Pin the current thread.
- process->PinCurrentThread(core_id);
+ process->PinCurrentThread();
// Set the interrupt flag for the thread.
GetCurrentThread(kernel).SetInterruptFlag();
diff --git a/src/core/hle/kernel/k_memory_manager.cpp b/src/core/hle/kernel/k_memory_manager.cpp
index 637558e10..cdc5572d8 100644
--- a/src/core/hle/kernel/k_memory_manager.cpp
+++ b/src/core/hle/kernel/k_memory_manager.cpp
@@ -11,6 +11,7 @@
#include "core/hle/kernel/initial_process.h"
#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/kernel/k_page_group.h"
+#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_results.h"
@@ -168,11 +169,37 @@ void KMemoryManager::Initialize(KVirtualAddress management_region, size_t manage
}
Result KMemoryManager::InitializeOptimizedMemory(u64 process_id, Pool pool) {
- UNREACHABLE();
+ const u32 pool_index = static_cast<u32>(pool);
+
+ // Lock the pool.
+ KScopedLightLock lk(m_pool_locks[pool_index]);
+
+ // Check that we don't already have an optimized process.
+ R_UNLESS(!m_has_optimized_process[pool_index], ResultBusy);
+
+ // Set the optimized process id.
+ m_optimized_process_ids[pool_index] = process_id;
+ m_has_optimized_process[pool_index] = true;
+
+ // Clear the management area for the optimized process.
+ for (auto* manager = this->GetFirstManager(pool, Direction::FromFront); manager != nullptr;
+ manager = this->GetNextManager(manager, Direction::FromFront)) {
+ manager->InitializeOptimizedMemory(m_system.Kernel());
+ }
+
+ R_SUCCEED();
}
void KMemoryManager::FinalizeOptimizedMemory(u64 process_id, Pool pool) {
- UNREACHABLE();
+ const u32 pool_index = static_cast<u32>(pool);
+
+ // Lock the pool.
+ KScopedLightLock lk(m_pool_locks[pool_index]);
+
+ // If the process was optimized, clear it.
+ if (m_has_optimized_process[pool_index] && m_optimized_process_ids[pool_index] == process_id) {
+ m_has_optimized_process[pool_index] = false;
+ }
}
KPhysicalAddress KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_pages,
@@ -207,7 +234,7 @@ KPhysicalAddress KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, siz
// Maintain the optimized memory bitmap, if we should.
if (m_has_optimized_process[static_cast<size_t>(pool)]) {
- UNIMPLEMENTED();
+ chosen_manager->TrackUnoptimizedAllocation(m_system.Kernel(), allocated_block, num_pages);
}
// Open the first reference to the pages.
@@ -255,7 +282,8 @@ Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages,
// Maintain the optimized memory bitmap, if we should.
if (unoptimized) {
- UNIMPLEMENTED();
+ cur_manager->TrackUnoptimizedAllocation(m_system.Kernel(), allocated_block,
+ pages_per_alloc);
}
num_pages -= pages_per_alloc;
@@ -358,8 +386,8 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32
// Process part or all of the block.
const size_t cur_pages =
std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
- any_new =
- manager.ProcessOptimizedAllocation(cur_address, cur_pages, fill_pattern);
+ any_new = manager.ProcessOptimizedAllocation(m_system.Kernel(), cur_address,
+ cur_pages, fill_pattern);
// Advance.
cur_address += cur_pages * PageSize;
@@ -382,7 +410,7 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32
// Track some or all of the current pages.
const size_t cur_pages =
std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
- manager.TrackOptimizedAllocation(cur_address, cur_pages);
+ manager.TrackOptimizedAllocation(m_system.Kernel(), cur_address, cur_pages);
// Advance.
cur_address += cur_pages * PageSize;
@@ -427,17 +455,86 @@ size_t KMemoryManager::Impl::Initialize(KPhysicalAddress address, size_t size,
return total_management_size;
}
-void KMemoryManager::Impl::TrackUnoptimizedAllocation(KPhysicalAddress block, size_t num_pages) {
- UNREACHABLE();
+void KMemoryManager::Impl::InitializeOptimizedMemory(KernelCore& kernel) {
+ auto optimize_pa =
+ KPageTable::GetHeapPhysicalAddress(kernel.MemoryLayout(), m_management_region);
+ auto* optimize_map = kernel.System().DeviceMemory().GetPointer<u64>(optimize_pa);
+
+ std::memset(optimize_map, 0, CalculateOptimizedProcessOverheadSize(m_heap.GetSize()));
}
-void KMemoryManager::Impl::TrackOptimizedAllocation(KPhysicalAddress block, size_t num_pages) {
- UNREACHABLE();
+void KMemoryManager::Impl::TrackUnoptimizedAllocation(KernelCore& kernel, KPhysicalAddress block,
+ size_t num_pages) {
+ auto optimize_pa =
+ KPageTable::GetHeapPhysicalAddress(kernel.MemoryLayout(), m_management_region);
+ auto* optimize_map = kernel.System().DeviceMemory().GetPointer<u64>(optimize_pa);
+
+ // Get the range we're tracking.
+ size_t offset = this->GetPageOffset(block);
+ const size_t last = offset + num_pages - 1;
+
+ // Track.
+ while (offset <= last) {
+ // Mark the page as not being optimized-allocated.
+ optimize_map[offset / Common::BitSize<u64>()] &=
+ ~(u64(1) << (offset % Common::BitSize<u64>()));
+
+ offset++;
+ }
+}
+
+void KMemoryManager::Impl::TrackOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block,
+ size_t num_pages) {
+ auto optimize_pa =
+ KPageTable::GetHeapPhysicalAddress(kernel.MemoryLayout(), m_management_region);
+ auto* optimize_map = kernel.System().DeviceMemory().GetPointer<u64>(optimize_pa);
+
+ // Get the range we're tracking.
+ size_t offset = this->GetPageOffset(block);
+ const size_t last = offset + num_pages - 1;
+
+ // Track.
+ while (offset <= last) {
+ // Mark the page as being optimized-allocated.
+ optimize_map[offset / Common::BitSize<u64>()] |=
+ (u64(1) << (offset % Common::BitSize<u64>()));
+
+ offset++;
+ }
}
-bool KMemoryManager::Impl::ProcessOptimizedAllocation(KPhysicalAddress block, size_t num_pages,
- u8 fill_pattern) {
- UNREACHABLE();
+bool KMemoryManager::Impl::ProcessOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block,
+ size_t num_pages, u8 fill_pattern) {
+ auto& device_memory = kernel.System().DeviceMemory();
+ auto optimize_pa =
+ KPageTable::GetHeapPhysicalAddress(kernel.MemoryLayout(), m_management_region);
+ auto* optimize_map = device_memory.GetPointer<u64>(optimize_pa);
+
+ // We want to return whether any pages were newly allocated.
+ bool any_new = false;
+
+ // Get the range we're processing.
+ size_t offset = this->GetPageOffset(block);
+ const size_t last = offset + num_pages - 1;
+
+ // Process.
+ while (offset <= last) {
+ // Check if the page has been optimized-allocated before.
+ if ((optimize_map[offset / Common::BitSize<u64>()] &
+ (u64(1) << (offset % Common::BitSize<u64>()))) == 0) {
+ // If not, it's new.
+ any_new = true;
+
+ // Fill the page.
+ auto* ptr = device_memory.GetPointer<u8>(m_heap.GetAddress());
+ std::memset(ptr + offset * PageSize, fill_pattern, PageSize);
+ }
+
+ offset++;
+ }
+
+ // Return the number of pages we processed.
+ return any_new;
}
size_t KMemoryManager::Impl::CalculateManagementOverheadSize(size_t region_size) {
diff --git a/src/core/hle/kernel/k_memory_manager.h b/src/core/hle/kernel/k_memory_manager.h
index 7e4b41319..c5a487af9 100644
--- a/src/core/hle/kernel/k_memory_manager.h
+++ b/src/core/hle/kernel/k_memory_manager.h
@@ -216,14 +216,14 @@ private:
m_heap.SetInitialUsedSize(reserved_size);
}
- void InitializeOptimizedMemory() {
- UNIMPLEMENTED();
- }
+ void InitializeOptimizedMemory(KernelCore& kernel);
- void TrackUnoptimizedAllocation(KPhysicalAddress block, size_t num_pages);
- void TrackOptimizedAllocation(KPhysicalAddress block, size_t num_pages);
+ void TrackUnoptimizedAllocation(KernelCore& kernel, KPhysicalAddress block,
+ size_t num_pages);
+ void TrackOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block, size_t num_pages);
- bool ProcessOptimizedAllocation(KPhysicalAddress block, size_t num_pages, u8 fill_pattern);
+ bool ProcessOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block,
+ size_t num_pages, u8 fill_pattern);
constexpr Pool GetPool() const {
return m_pool;
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index 217ccbae3..1d47bdf6b 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -82,14 +82,14 @@ public:
using namespace Common::Literals;
-constexpr size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceType as_type) {
+constexpr size_t GetAddressSpaceWidthFromType(Svc::CreateProcessFlag as_type) {
switch (as_type) {
- case FileSys::ProgramAddressSpaceType::Is32Bit:
- case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
+ case Svc::CreateProcessFlag::AddressSpace32Bit:
+ case Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias:
return 32;
- case FileSys::ProgramAddressSpaceType::Is36Bit:
+ case Svc::CreateProcessFlag::AddressSpace64BitDeprecated:
return 36;
- case FileSys::ProgramAddressSpaceType::Is39Bit:
+ case Svc::CreateProcessFlag::AddressSpace64Bit:
return 39;
default:
ASSERT(false);
@@ -105,7 +105,7 @@ KPageTable::KPageTable(Core::System& system_)
KPageTable::~KPageTable() = default;
-Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr,
+Result KPageTable::InitializeForProcess(Svc::CreateProcessFlag as_type, bool enable_aslr,
bool enable_das_merge, bool from_back,
KMemoryManager::Pool pool, KProcessAddress code_addr,
size_t code_size, KSystemResource* system_resource,
@@ -133,7 +133,7 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type
ASSERT(code_addr + code_size - 1 <= end - 1);
// Adjust heap/alias size if we don't have an alias region
- if (as_type == FileSys::ProgramAddressSpaceType::Is32BitNoMap) {
+ if (as_type == Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias) {
heap_region_size += alias_region_size;
alias_region_size = 0;
}
diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h
index 3d64b6fb0..66f16faaf 100644
--- a/src/core/hle/kernel/k_page_table.h
+++ b/src/core/hle/kernel/k_page_table.h
@@ -63,7 +63,7 @@ public:
explicit KPageTable(Core::System& system_);
~KPageTable();
- Result InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr,
+ Result InitializeForProcess(Svc::CreateProcessFlag as_type, bool enable_aslr,
bool enable_das_merge, bool from_back, KMemoryManager::Pool pool,
KProcessAddress code_addr, size_t code_size,
KSystemResource* system_resource, KResourceLimit* resource_limit,
@@ -400,7 +400,7 @@ public:
constexpr size_t GetAliasCodeRegionSize() const {
return m_alias_code_region_end - m_alias_code_region_start;
}
- size_t GetNormalMemorySize() {
+ size_t GetNormalMemorySize() const {
KScopedLightLock lk(m_general_lock);
return GetHeapSize() + m_mapped_physical_memory_size;
}
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 7fa34d693..1f4b0755d 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -1,515 +1,598 @@
-// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <algorithm>
-#include <bitset>
-#include <ctime>
-#include <memory>
#include <random>
-#include "common/alignment.h"
-#include "common/assert.h"
-#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/settings.h"
#include "core/core.h"
-#include "core/file_sys/program_metadata.h"
-#include "core/hle/kernel/code_set.h"
-#include "core/hle/kernel/k_memory_block_manager.h"
-#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h"
-#include "core/hle/kernel/k_resource_limit.h"
-#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/k_shared_memory_info.h"
-#include "core/hle/kernel/k_thread.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/svc_results.h"
-#include "core/memory.h"
+#include "core/hle/kernel/k_thread_local_page.h"
+#include "core/hle/kernel/k_thread_queue.h"
+#include "core/hle/kernel/k_worker_task_manager.h"
namespace Kernel {
-namespace {
-/**
- * Sets up the primary application thread
- *
- * @param system The system instance to create the main thread under.
- * @param owner_process The parent process for the main thread
- * @param priority The priority to give the main thread
- */
-void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority,
- KProcessAddress stack_top) {
- const KProcessAddress entry_point = owner_process.GetEntryPoint();
- ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::ThreadCountMax, 1));
-
- KThread* thread = KThread::Create(system.Kernel());
- SCOPE_EXIT({ thread->Close(); });
-
- ASSERT(KThread::InitializeUserThread(system, thread, entry_point, 0, stack_top, priority,
- owner_process.GetIdealCoreId(),
- std::addressof(owner_process))
- .IsSuccess());
-
- // Register 1 must be a handle to the main thread
- Handle thread_handle{};
- owner_process.GetHandleTable().Add(std::addressof(thread_handle), thread);
-
- thread->GetContext32().cpu_registers[0] = 0;
- thread->GetContext64().cpu_registers[0] = 0;
- thread->GetContext32().cpu_registers[1] = thread_handle;
- thread->GetContext64().cpu_registers[1] = thread_handle;
-
- if (system.DebuggerEnabled()) {
- thread->RequestSuspend(SuspendType::Debug);
- }
- // Run our thread.
- void(thread->Run());
-}
-} // Anonymous namespace
+namespace {
-Result KProcess::Initialize(KProcess* process, Core::System& system, std::string process_name,
- ProcessType type, KResourceLimit* res_limit) {
- auto& kernel = system.Kernel();
+Result TerminateChildren(KernelCore& kernel, KProcess* process,
+ const KThread* thread_to_not_terminate) {
+ // Request that all children threads terminate.
+ {
+ KScopedLightLock proc_lk(process->GetListLock());
+ KScopedSchedulerLock sl(kernel);
+
+ if (thread_to_not_terminate != nullptr &&
+ process->GetPinnedThread(GetCurrentCoreId(kernel)) == thread_to_not_terminate) {
+ // NOTE: Here Nintendo unpins the current thread instead of the thread_to_not_terminate.
+ // This is valid because the only caller which uses non-nullptr as argument uses
+ // GetCurrentThreadPointer(), but it's still notable because it seems incorrect at
+ // first glance.
+ process->UnpinCurrentThread();
+ }
- process->name = std::move(process_name);
- process->m_resource_limit = res_limit;
- process->m_system_resource_address = 0;
- process->m_state = State::Created;
- process->m_program_id = 0;
- process->m_process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID()
- : kernel.CreateNewUserProcessID();
- process->m_capabilities.InitializeForMetadatalessProcess();
- process->m_is_initialized = true;
+ auto& thread_list = process->GetThreadList();
+ for (auto it = thread_list.begin(); it != thread_list.end(); ++it) {
+ if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) {
+ if (thread->GetState() != ThreadState::Terminated) {
+ thread->RequestTerminate();
+ }
+ }
+ }
+ }
- std::mt19937 rng(Settings::values.rng_seed_enabled ? Settings::values.rng_seed.GetValue()
- : static_cast<u32>(std::time(nullptr)));
- std::uniform_int_distribution<u64> distribution;
- std::generate(process->m_random_entropy.begin(), process->m_random_entropy.end(),
- [&] { return distribution(rng); });
+ // Wait for all children threads to terminate.
+ while (true) {
+ // Get the next child.
+ KThread* cur_child = nullptr;
+ {
+ KScopedLightLock proc_lk(process->GetListLock());
+
+ auto& thread_list = process->GetThreadList();
+ for (auto it = thread_list.begin(); it != thread_list.end(); ++it) {
+ if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) {
+ if (thread->GetState() != ThreadState::Terminated) {
+ if (thread->Open()) {
+ cur_child = thread;
+ break;
+ }
+ }
+ }
+ }
+ }
- kernel.AppendNewProcess(process);
+ // If we didn't find any non-terminated children, we're done.
+ if (cur_child == nullptr) {
+ break;
+ }
- // Clear remaining fields.
- process->m_num_running_threads = 0;
- process->m_is_signaled = false;
- process->m_exception_thread = nullptr;
- process->m_is_suspended = false;
- process->m_schedule_count = 0;
- process->m_is_handle_table_initialized = false;
- process->m_is_hbl = false;
+ // Terminate and close the thread.
+ SCOPE_EXIT({ cur_child->Close(); });
- // Open a reference to the resource limit.
- process->m_resource_limit->Open();
+ if (const Result terminate_result = cur_child->Terminate();
+ ResultTerminationRequested == terminate_result) {
+ R_THROW(terminate_result);
+ }
+ }
R_SUCCEED();
}
-void KProcess::DoWorkerTaskImpl() {
- UNIMPLEMENTED();
-}
-
-KResourceLimit* KProcess::GetResourceLimit() const {
- return m_resource_limit;
-}
+class ThreadQueueImplForKProcessEnterUserException final : public KThreadQueue {
+private:
+ KThread** m_exception_thread;
-void KProcess::IncrementRunningThreadCount() {
- ASSERT(m_num_running_threads.load() >= 0);
- ++m_num_running_threads;
-}
+public:
+ explicit ThreadQueueImplForKProcessEnterUserException(KernelCore& kernel, KThread** t)
+ : KThreadQueue(kernel), m_exception_thread(t) {}
-void KProcess::DecrementRunningThreadCount() {
- ASSERT(m_num_running_threads.load() > 0);
+ virtual void EndWait(KThread* waiting_thread, Result wait_result) override {
+ // Set the exception thread.
+ *m_exception_thread = waiting_thread;
- if (const auto prev = m_num_running_threads--; prev == 1) {
- // TODO(bunnei): Process termination to be implemented when multiprocess is supported.
+ // Invoke the base end wait handler.
+ KThreadQueue::EndWait(waiting_thread, wait_result);
}
-}
-u64 KProcess::GetTotalPhysicalMemoryAvailable() {
- const u64 capacity{m_resource_limit->GetFreeValue(LimitableResource::PhysicalMemoryMax) +
- m_page_table.GetNormalMemorySize() + GetSystemResourceSize() + m_image_size +
- m_main_thread_stack_size};
- if (const auto pool_size = m_kernel.MemoryManager().GetSize(KMemoryManager::Pool::Application);
- capacity != pool_size) {
- LOG_WARNING(Kernel, "capacity {} != application pool size {}", capacity, pool_size);
- }
- if (capacity < m_memory_usage_capacity) {
- return capacity;
+ virtual void CancelWait(KThread* waiting_thread, Result wait_result,
+ bool cancel_timer_task) override {
+ // Remove the thread as a waiter on its mutex owner.
+ waiting_thread->GetLockOwner()->RemoveWaiter(waiting_thread);
+
+ // Invoke the base cancel wait handler.
+ KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
}
- return m_memory_usage_capacity;
-}
+};
-u64 KProcess::GetTotalPhysicalMemoryAvailableWithoutSystemResource() {
- return this->GetTotalPhysicalMemoryAvailable() - this->GetSystemResourceSize();
+void GenerateRandom(std::span<u64> out_random) {
+ std::mt19937 rng(Settings::values.rng_seed_enabled ? Settings::values.rng_seed.GetValue()
+ : static_cast<u32>(std::time(nullptr)));
+ std::uniform_int_distribution<u64> distribution;
+ std::generate(out_random.begin(), out_random.end(), [&] { return distribution(rng); });
}
-u64 KProcess::GetTotalPhysicalMemoryUsed() {
- return m_image_size + m_main_thread_stack_size + m_page_table.GetNormalMemorySize() +
- this->GetSystemResourceSize();
-}
+} // namespace
-u64 KProcess::GetTotalPhysicalMemoryUsedWithoutSystemResource() {
- return this->GetTotalPhysicalMemoryUsed() - this->GetSystemResourceSize();
-}
+void KProcess::Finalize() {
+ // Delete the process local region.
+ this->DeleteThreadLocalRegion(m_plr_address);
-bool KProcess::ReleaseUserException(KThread* thread) {
- KScopedSchedulerLock sl{m_kernel};
+ // Get the used memory size.
+ const size_t used_memory_size = this->GetUsedNonSystemUserPhysicalMemorySize();
- if (m_exception_thread == thread) {
- m_exception_thread = nullptr;
+ // Finalize the page table.
+ m_page_table.Finalize();
- // Remove waiter thread.
- bool has_waiters{};
- if (KThread* next = thread->RemoveKernelWaiterByKey(
- std::addressof(has_waiters),
- reinterpret_cast<uintptr_t>(std::addressof(m_exception_thread)));
- next != nullptr) {
- next->EndWait(ResultSuccess);
+ // Finish using our system resource.
+ if (m_system_resource) {
+ if (m_system_resource->IsSecureResource()) {
+ // Finalize optimized memory. If memory wasn't optimized, this is a no-op.
+ m_kernel.MemoryManager().FinalizeOptimizedMemory(this->GetId(), m_memory_pool);
}
- KScheduler::SetSchedulerUpdateNeeded(m_kernel);
-
- return true;
- } else {
- return false;
+ m_system_resource->Close();
+ m_system_resource = nullptr;
}
-}
-
-void KProcess::PinCurrentThread(s32 core_id) {
- ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
- // Get the current thread.
- KThread* cur_thread =
- m_kernel.Scheduler(static_cast<std::size_t>(core_id)).GetSchedulerCurrentThread();
+ // Free all shared memory infos.
+ {
+ auto it = m_shared_memory_list.begin();
+ while (it != m_shared_memory_list.end()) {
+ KSharedMemoryInfo* info = std::addressof(*it);
+ KSharedMemory* shmem = info->GetSharedMemory();
- // If the thread isn't terminated, pin it.
- if (!cur_thread->IsTerminationRequested()) {
- // Pin it.
- this->PinThread(core_id, cur_thread);
- cur_thread->Pin(core_id);
+ while (!info->Close()) {
+ shmem->Close();
+ }
+ shmem->Close();
- // An update is needed.
- KScheduler::SetSchedulerUpdateNeeded(m_kernel);
+ it = m_shared_memory_list.erase(it);
+ KSharedMemoryInfo::Free(m_kernel, info);
+ }
}
-}
-void KProcess::UnpinCurrentThread(s32 core_id) {
- ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
-
- // Get the current thread.
- KThread* cur_thread =
- m_kernel.Scheduler(static_cast<std::size_t>(core_id)).GetSchedulerCurrentThread();
+ // Our thread local page list must be empty at this point.
+ ASSERT(m_partially_used_tlp_tree.empty());
+ ASSERT(m_fully_used_tlp_tree.empty());
- // Unpin it.
- cur_thread->Unpin();
- this->UnpinThread(core_id, cur_thread);
+ // Release memory to the resource limit.
+ if (m_resource_limit != nullptr) {
+ ASSERT(used_memory_size >= m_memory_release_hint);
+ m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, used_memory_size,
+ used_memory_size - m_memory_release_hint);
+ m_resource_limit->Close();
+ }
- // An update is needed.
- KScheduler::SetSchedulerUpdateNeeded(m_kernel);
+ // Perform inherited finalization.
+ KSynchronizationObject::Finalize();
}
-void KProcess::UnpinThread(KThread* thread) {
- ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
-
- // Get the thread's core id.
- const auto core_id = thread->GetActiveCore();
+Result KProcess::Initialize(const Svc::CreateProcessParameter& params, KResourceLimit* res_limit,
+ bool is_real) {
+ // TODO: remove this special case
+ if (is_real) {
+ // Create and clear the process local region.
+ R_TRY(this->CreateThreadLocalRegion(std::addressof(m_plr_address)));
+ this->GetMemory().ZeroBlock(m_plr_address, Svc::ThreadLocalRegionSize);
+ }
- // Unpin it.
- this->UnpinThread(core_id, thread);
- thread->Unpin();
+ // Copy in the name from parameters.
+ static_assert(sizeof(params.name) < sizeof(m_name));
+ std::memcpy(m_name.data(), params.name.data(), sizeof(params.name));
+ m_name[sizeof(params.name)] = 0;
+
+ // Set misc fields.
+ m_state = State::Created;
+ m_main_thread_stack_size = 0;
+ m_used_kernel_memory_size = 0;
+ m_ideal_core_id = 0;
+ m_flags = params.flags;
+ m_version = params.version;
+ m_program_id = params.program_id;
+ m_code_address = params.code_address;
+ m_code_size = params.code_num_pages * PageSize;
+ m_is_application = True(params.flags & Svc::CreateProcessFlag::IsApplication);
+
+ // Set thread fields.
+ for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
+ m_running_threads[i] = nullptr;
+ m_pinned_threads[i] = nullptr;
+ m_running_thread_idle_counts[i] = 0;
+ m_running_thread_switch_counts[i] = 0;
+ }
- // An update is needed.
- KScheduler::SetSchedulerUpdateNeeded(m_kernel);
-}
+ // Set max memory based on address space type.
+ switch ((params.flags & Svc::CreateProcessFlag::AddressSpaceMask)) {
+ case Svc::CreateProcessFlag::AddressSpace32Bit:
+ case Svc::CreateProcessFlag::AddressSpace64BitDeprecated:
+ case Svc::CreateProcessFlag::AddressSpace64Bit:
+ m_max_process_memory = m_page_table.GetHeapRegionSize();
+ break;
+ case Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias:
+ m_max_process_memory = m_page_table.GetHeapRegionSize() + m_page_table.GetAliasRegionSize();
+ break;
+ default:
+ UNREACHABLE();
+ }
-Result KProcess::AddSharedMemory(KSharedMemory* shmem, [[maybe_unused]] KProcessAddress address,
- [[maybe_unused]] size_t size) {
- // Lock ourselves, to prevent concurrent access.
- KScopedLightLock lk(m_state_lock);
+ // Generate random entropy.
+ GenerateRandom(m_entropy);
- // Try to find an existing info for the memory.
- KSharedMemoryInfo* shemen_info = nullptr;
- const auto iter = std::find_if(
- m_shared_memory_list.begin(), m_shared_memory_list.end(),
- [shmem](const KSharedMemoryInfo* info) { return info->GetSharedMemory() == shmem; });
- if (iter != m_shared_memory_list.end()) {
- shemen_info = *iter;
- }
+ // Clear remaining fields.
+ m_num_running_threads = 0;
+ m_num_process_switches = 0;
+ m_num_thread_switches = 0;
+ m_num_fpu_switches = 0;
+ m_num_supervisor_calls = 0;
+ m_num_ipc_messages = 0;
- if (shemen_info == nullptr) {
- shemen_info = KSharedMemoryInfo::Allocate(m_kernel);
- R_UNLESS(shemen_info != nullptr, ResultOutOfMemory);
+ m_is_signaled = false;
+ m_exception_thread = nullptr;
+ m_is_suspended = false;
+ m_memory_release_hint = 0;
+ m_schedule_count = 0;
+ m_is_handle_table_initialized = false;
- shemen_info->Initialize(shmem);
- m_shared_memory_list.push_back(shemen_info);
- }
+ // Open a reference to our resource limit.
+ m_resource_limit = res_limit;
+ m_resource_limit->Open();
- // Open a reference to the shared memory and its info.
- shmem->Open();
- shemen_info->Open();
+ // We're initialized!
+ m_is_initialized = true;
R_SUCCEED();
}
-void KProcess::RemoveSharedMemory(KSharedMemory* shmem, [[maybe_unused]] KProcessAddress address,
- [[maybe_unused]] size_t size) {
- // Lock ourselves, to prevent concurrent access.
- KScopedLightLock lk(m_state_lock);
+Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPageGroup& pg,
+ std::span<const u32> caps, KResourceLimit* res_limit,
+ KMemoryManager::Pool pool, bool immortal) {
+ ASSERT(res_limit != nullptr);
+ ASSERT((params.code_num_pages * PageSize) / PageSize ==
+ static_cast<size_t>(params.code_num_pages));
+
+ // Set members.
+ m_memory_pool = pool;
+ m_is_default_application_system_resource = false;
+ m_is_immortal = immortal;
+
+ // Setup our system resource.
+ if (const size_t system_resource_num_pages = params.system_resource_num_pages;
+ system_resource_num_pages != 0) {
+ // Create a secure system resource.
+ KSecureSystemResource* secure_resource = KSecureSystemResource::Create(m_kernel);
+ R_UNLESS(secure_resource != nullptr, ResultOutOfResource);
+
+ ON_RESULT_FAILURE {
+ secure_resource->Close();
+ };
+
+ // Initialize the secure resource.
+ R_TRY(secure_resource->Initialize(system_resource_num_pages * PageSize, res_limit,
+ m_memory_pool));
+
+ // Set our system resource.
+ m_system_resource = secure_resource;
+ } else {
+ // Use the system-wide system resource.
+ const bool is_app = True(params.flags & Svc::CreateProcessFlag::IsApplication);
+ m_system_resource = std::addressof(is_app ? m_kernel.GetAppSystemResource()
+ : m_kernel.GetSystemSystemResource());
- KSharedMemoryInfo* shemen_info = nullptr;
- const auto iter = std::find_if(
- m_shared_memory_list.begin(), m_shared_memory_list.end(),
- [shmem](const KSharedMemoryInfo* info) { return info->GetSharedMemory() == shmem; });
- if (iter != m_shared_memory_list.end()) {
- shemen_info = *iter;
+ m_is_default_application_system_resource = is_app;
+
+ // Open reference to the system resource.
+ m_system_resource->Open();
}
- ASSERT(shemen_info != nullptr);
+ // Ensure we clean up our secure resource, if we fail.
+ ON_RESULT_FAILURE {
+ m_system_resource->Close();
+ m_system_resource = nullptr;
+ };
- if (shemen_info->Close()) {
- m_shared_memory_list.erase(iter);
- KSharedMemoryInfo::Free(m_kernel, shemen_info);
+ // Setup page table.
+ {
+ const auto as_type = params.flags & Svc::CreateProcessFlag::AddressSpaceMask;
+ const bool enable_aslr = True(params.flags & Svc::CreateProcessFlag::EnableAslr);
+ const bool enable_das_merge =
+ False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge);
+ R_TRY(m_page_table.InitializeForProcess(
+ as_type, enable_aslr, enable_das_merge, !enable_aslr, pool, params.code_address,
+ params.code_num_pages * PageSize, m_system_resource, res_limit, this->GetMemory()));
}
+ ON_RESULT_FAILURE_2 {
+ m_page_table.Finalize();
+ };
- // Close a reference to the shared memory.
- shmem->Close();
-}
+ // Ensure we can insert the code region.
+ R_UNLESS(m_page_table.CanContain(params.code_address, params.code_num_pages * PageSize,
+ KMemoryState::Code),
+ ResultInvalidMemoryRegion);
-void KProcess::RegisterThread(KThread* thread) {
- KScopedLightLock lk{m_list_lock};
+ // Map the code region.
+ R_TRY(m_page_table.MapPageGroup(params.code_address, pg, KMemoryState::Code,
+ KMemoryPermission::KernelRead));
- m_thread_list.push_back(thread);
-}
+ // Initialize capabilities.
+ R_TRY(m_capabilities.InitializeForKip(caps, std::addressof(m_page_table)));
-void KProcess::UnregisterThread(KThread* thread) {
- KScopedLightLock lk{m_list_lock};
+ // Initialize the process id.
+ m_process_id = m_kernel.CreateNewUserProcessID();
+ ASSERT(InitialProcessIdMin <= m_process_id);
+ ASSERT(m_process_id <= InitialProcessIdMax);
- m_thread_list.remove(thread);
-}
+ // Initialize the rest of the process.
+ R_TRY(this->Initialize(params, res_limit, true));
-u64 KProcess::GetFreeThreadCount() const {
- if (m_resource_limit != nullptr) {
- const auto current_value =
- m_resource_limit->GetCurrentValue(LimitableResource::ThreadCountMax);
- const auto limit_value = m_resource_limit->GetLimitValue(LimitableResource::ThreadCountMax);
- return limit_value - current_value;
- } else {
- return 0;
- }
+ // We succeeded!
+ R_SUCCEED();
}
-Result KProcess::Reset() {
- // Lock the process and the scheduler.
- KScopedLightLock lk(m_state_lock);
- KScopedSchedulerLock sl{m_kernel};
+Result KProcess::Initialize(const Svc::CreateProcessParameter& params,
+ std::span<const u32> user_caps, KResourceLimit* res_limit,
+ KMemoryManager::Pool pool) {
+ ASSERT(res_limit != nullptr);
- // Validate that we're in a state that we can reset.
- R_UNLESS(m_state != State::Terminated, ResultInvalidState);
- R_UNLESS(m_is_signaled, ResultInvalidState);
+ // Set members.
+ m_memory_pool = pool;
+ m_is_default_application_system_resource = false;
+ m_is_immortal = false;
- // Clear signaled.
- m_is_signaled = false;
- R_SUCCEED();
-}
+ // Get the memory sizes.
+ const size_t code_num_pages = params.code_num_pages;
+ const size_t system_resource_num_pages = params.system_resource_num_pages;
+ const size_t code_size = code_num_pages * PageSize;
+ const size_t system_resource_size = system_resource_num_pages * PageSize;
-Result KProcess::SetActivity(ProcessActivity activity) {
- // Lock ourselves and the scheduler.
- KScopedLightLock lk{m_state_lock};
- KScopedLightLock list_lk{m_list_lock};
- KScopedSchedulerLock sl{m_kernel};
+ // Reserve memory for our code resource.
+ KScopedResourceReservation memory_reservation(
+ res_limit, Svc::LimitableResource::PhysicalMemoryMax, code_size);
+ R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
- // Validate our state.
- R_UNLESS(m_state != State::Terminating, ResultInvalidState);
- R_UNLESS(m_state != State::Terminated, ResultInvalidState);
+ // Setup our system resource.
+ if (system_resource_num_pages != 0) {
+ // Create a secure system resource.
+ KSecureSystemResource* secure_resource = KSecureSystemResource::Create(m_kernel);
+ R_UNLESS(secure_resource != nullptr, ResultOutOfResource);
- // Either pause or resume.
- if (activity == ProcessActivity::Paused) {
- // Verify that we're not suspended.
- R_UNLESS(!m_is_suspended, ResultInvalidState);
+ ON_RESULT_FAILURE {
+ secure_resource->Close();
+ };
- // Suspend all threads.
- for (auto* thread : this->GetThreadList()) {
- thread->RequestSuspend(SuspendType::Process);
- }
+ // Initialize the secure resource.
+ R_TRY(secure_resource->Initialize(system_resource_size, res_limit, m_memory_pool));
+
+ // Set our system resource.
+ m_system_resource = secure_resource;
- // Set ourselves as suspended.
- this->SetSuspended(true);
} else {
- ASSERT(activity == ProcessActivity::Runnable);
+ // Use the system-wide system resource.
+ const bool is_app = True(params.flags & Svc::CreateProcessFlag::IsApplication);
+ m_system_resource = std::addressof(is_app ? m_kernel.GetAppSystemResource()
+ : m_kernel.GetSystemSystemResource());
- // Verify that we're suspended.
- R_UNLESS(m_is_suspended, ResultInvalidState);
+ m_is_default_application_system_resource = is_app;
- // Resume all threads.
- for (auto* thread : this->GetThreadList()) {
- thread->Resume(SuspendType::Process);
- }
+ // Open reference to the system resource.
+ m_system_resource->Open();
+ }
- // Set ourselves as resumed.
- this->SetSuspended(false);
+ // Ensure we clean up our secure resource, if we fail.
+ ON_RESULT_FAILURE {
+ m_system_resource->Close();
+ m_system_resource = nullptr;
+ };
+
+ // Setup page table.
+ {
+ const auto as_type = params.flags & Svc::CreateProcessFlag::AddressSpaceMask;
+ const bool enable_aslr = True(params.flags & Svc::CreateProcessFlag::EnableAslr);
+ const bool enable_das_merge =
+ False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge);
+ R_TRY(m_page_table.InitializeForProcess(as_type, enable_aslr, enable_das_merge,
+ !enable_aslr, pool, params.code_address, code_size,
+ m_system_resource, res_limit, this->GetMemory()));
+ }
+ ON_RESULT_FAILURE_2 {
+ m_page_table.Finalize();
+ };
+
+ // Ensure we can insert the code region.
+ R_UNLESS(m_page_table.CanContain(params.code_address, code_size, KMemoryState::Code),
+ ResultInvalidMemoryRegion);
+
+ // Map the code region.
+ R_TRY(m_page_table.MapPages(params.code_address, code_num_pages, KMemoryState::Code,
+ KMemoryPermission::KernelRead | KMemoryPermission::NotMapped));
+
+ // Initialize capabilities.
+ R_TRY(m_capabilities.InitializeForUser(user_caps, std::addressof(m_page_table)));
+
+ // Initialize the process id.
+ m_process_id = m_kernel.CreateNewUserProcessID();
+ ASSERT(ProcessIdMin <= m_process_id);
+ ASSERT(m_process_id <= ProcessIdMax);
+
+ // If we should optimize memory allocations, do so.
+ if (m_system_resource->IsSecureResource() &&
+ True(params.flags & Svc::CreateProcessFlag::OptimizeMemoryAllocation)) {
+ R_TRY(m_kernel.MemoryManager().InitializeOptimizedMemory(m_process_id, pool));
}
+ // Initialize the rest of the process.
+ R_TRY(this->Initialize(params, res_limit, true));
+
+ // We succeeded, so commit our memory reservation.
+ memory_reservation.Commit();
R_SUCCEED();
}
-Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
- bool is_hbl) {
- m_program_id = metadata.GetTitleID();
- m_ideal_core = metadata.GetMainThreadCore();
- m_is_64bit_process = metadata.Is64BitProgram();
- m_system_resource_size = metadata.GetSystemResourceSize();
- m_image_size = code_size;
- m_is_hbl = is_hbl;
+void KProcess::DoWorkerTaskImpl() {
+ // Terminate child threads.
+ TerminateChildren(m_kernel, this, nullptr);
- if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) {
- // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large.
- // However, some (buggy) programs/libraries like skyline incorrectly depend on the
- // existence of ASLR pages before the entry point, so we will adjust the load address
- // to point to about 2GiB into the ASLR region.
- m_code_address = 0x8000'0000;
- } else {
- // All other processes can be mapped at the beginning of the code region.
- if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is36Bit) {
- m_code_address = 0x800'0000;
- } else {
- m_code_address = 0x20'0000;
- }
+ // Finalize the handle table, if we're not immortal.
+ if (!m_is_immortal && m_is_handle_table_initialized) {
+ this->FinalizeHandleTable();
}
- KScopedResourceReservation memory_reservation(
- m_resource_limit, LimitableResource::PhysicalMemoryMax, code_size + m_system_resource_size);
- if (!memory_reservation.Succeeded()) {
- LOG_ERROR(Kernel, "Could not reserve process memory requirements of size {:X} bytes",
- code_size + m_system_resource_size);
- R_RETURN(ResultLimitReached);
- }
- // Initialize process address space
- if (const Result result{m_page_table.InitializeForProcess(
- metadata.GetAddressSpaceType(), false, false, false, KMemoryManager::Pool::Application,
- this->GetEntryPoint(), code_size, std::addressof(m_kernel.GetAppSystemResource()),
- m_resource_limit, m_kernel.System().ApplicationMemory())};
- result.IsError()) {
- R_RETURN(result);
- }
-
- // Map process code region
- if (const Result result{m_page_table.MapProcessCode(this->GetEntryPoint(), code_size / PageSize,
- KMemoryState::Code,
- KMemoryPermission::None)};
- result.IsError()) {
- R_RETURN(result);
- }
-
- // Initialize process capabilities
- const auto& caps{metadata.GetKernelCapabilities()};
- if (const Result result{
- m_capabilities.InitializeForUserProcess(caps.data(), caps.size(), m_page_table)};
- result.IsError()) {
- R_RETURN(result);
- }
-
- // Set memory usage capacity
- switch (metadata.GetAddressSpaceType()) {
- case FileSys::ProgramAddressSpaceType::Is32Bit:
- case FileSys::ProgramAddressSpaceType::Is36Bit:
- case FileSys::ProgramAddressSpaceType::Is39Bit:
- m_memory_usage_capacity =
- m_page_table.GetHeapRegionEnd() - m_page_table.GetHeapRegionStart();
- break;
+ // Finish termination.
+ this->FinishTermination();
+}
- case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
- m_memory_usage_capacity =
- (m_page_table.GetHeapRegionEnd() - m_page_table.GetHeapRegionStart()) +
- (m_page_table.GetAliasRegionEnd() - m_page_table.GetAliasRegionStart());
- break;
+Result KProcess::StartTermination() {
+ // Finalize the handle table when we're done, if the process isn't immortal.
+ SCOPE_EXIT({
+ if (!m_is_immortal) {
+ this->FinalizeHandleTable();
+ }
+ });
- default:
- ASSERT(false);
- break;
- }
+ // Terminate child threads other than the current one.
+ R_RETURN(TerminateChildren(m_kernel, this, GetCurrentThreadPointer(m_kernel)));
+}
- // Create TLS region
- R_TRY(this->CreateThreadLocalRegion(std::addressof(m_plr_address)));
- memory_reservation.Commit();
+void KProcess::FinishTermination() {
+ // Only allow termination to occur if the process isn't immortal.
+ if (!m_is_immortal) {
+ // Release resource limit hint.
+ if (m_resource_limit != nullptr) {
+ m_memory_release_hint = this->GetUsedNonSystemUserPhysicalMemorySize();
+ m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, 0,
+ m_memory_release_hint);
+ }
+
+ // Change state.
+ {
+ KScopedSchedulerLock sl(m_kernel);
+ this->ChangeState(State::Terminated);
+ }
- R_RETURN(m_handle_table.Initialize(m_capabilities.GetHandleTableSize()));
+ // Close.
+ this->Close();
+ }
}
-void KProcess::Run(s32 main_thread_priority, u64 stack_size) {
- ASSERT(this->AllocateMainThreadStack(stack_size) == ResultSuccess);
- m_resource_limit->Reserve(LimitableResource::ThreadCountMax, 1);
+void KProcess::Exit() {
+ // Determine whether we need to start terminating
+ bool needs_terminate = false;
+ {
+ KScopedLightLock lk(m_state_lock);
+ KScopedSchedulerLock sl(m_kernel);
+
+ ASSERT(m_state != State::Created);
+ ASSERT(m_state != State::CreatedAttached);
+ ASSERT(m_state != State::Crashed);
+ ASSERT(m_state != State::Terminated);
+ if (m_state == State::Running || m_state == State::RunningAttached ||
+ m_state == State::DebugBreak) {
+ this->ChangeState(State::Terminating);
+ needs_terminate = true;
+ }
+ }
- const std::size_t heap_capacity{m_memory_usage_capacity -
- (m_main_thread_stack_size + m_image_size)};
- ASSERT(!m_page_table.SetMaxHeapSize(heap_capacity).IsError());
+ // If we need to start termination, do so.
+ if (needs_terminate) {
+ this->StartTermination();
- this->ChangeState(State::Running);
+ // Register the process as a work task.
+ m_kernel.WorkerTaskManager().AddTask(m_kernel, KWorkerTaskManager::WorkerType::Exit, this);
+ }
- SetupMainThread(m_kernel.System(), *this, main_thread_priority, m_main_thread_stack_top);
+ // Exit the current thread.
+ GetCurrentThread(m_kernel).Exit();
}
-void KProcess::PrepareForTermination() {
- this->ChangeState(State::Terminating);
+Result KProcess::Terminate() {
+ // Determine whether we need to start terminating.
+ bool needs_terminate = false;
+ {
+ KScopedLightLock lk(m_state_lock);
- const auto stop_threads = [this](const std::vector<KThread*>& in_thread_list) {
- for (auto* thread : in_thread_list) {
- if (thread->GetOwnerProcess() != this)
- continue;
+ // Check whether we're allowed to terminate.
+ R_UNLESS(m_state != State::Created, ResultInvalidState);
+ R_UNLESS(m_state != State::CreatedAttached, ResultInvalidState);
- if (thread == GetCurrentThreadPointer(m_kernel))
- continue;
+ KScopedSchedulerLock sl(m_kernel);
- // TODO(Subv): When are the other running/ready threads terminated?
- ASSERT_MSG(thread->GetState() == ThreadState::Waiting,
- "Exiting processes with non-waiting threads is currently unimplemented");
+ if (m_state == State::Running || m_state == State::RunningAttached ||
+ m_state == State::Crashed || m_state == State::DebugBreak) {
+ this->ChangeState(State::Terminating);
+ needs_terminate = true;
+ }
+ }
- thread->Exit();
+ // If we need to terminate, do so.
+ if (needs_terminate) {
+ // Start termination.
+ if (R_SUCCEEDED(this->StartTermination())) {
+ // Finish termination.
+ this->FinishTermination();
+ } else {
+ // Register the process as a work task.
+ m_kernel.WorkerTaskManager().AddTask(m_kernel, KWorkerTaskManager::WorkerType::Exit,
+ this);
}
- };
+ }
- stop_threads(m_kernel.System().GlobalSchedulerContext().GetThreadList());
+ R_SUCCEED();
+}
- this->DeleteThreadLocalRegion(m_plr_address);
- m_plr_address = 0;
+Result KProcess::AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size) {
+ // Lock ourselves, to prevent concurrent access.
+ KScopedLightLock lk(m_state_lock);
- if (m_resource_limit) {
- m_resource_limit->Release(LimitableResource::PhysicalMemoryMax,
- m_main_thread_stack_size + m_image_size);
+ // Try to find an existing info for the memory.
+ KSharedMemoryInfo* info = nullptr;
+ for (auto it = m_shared_memory_list.begin(); it != m_shared_memory_list.end(); ++it) {
+ if (it->GetSharedMemory() == shmem) {
+ info = std::addressof(*it);
+ break;
+ }
}
- this->ChangeState(State::Terminated);
-}
+ // If we didn't find an info, create one.
+ if (info == nullptr) {
+ // Allocate a new info.
+ info = KSharedMemoryInfo::Allocate(m_kernel);
+ R_UNLESS(info != nullptr, ResultOutOfResource);
-void KProcess::Finalize() {
- // Free all shared memory infos.
- {
- auto it = m_shared_memory_list.begin();
- while (it != m_shared_memory_list.end()) {
- KSharedMemoryInfo* info = *it;
- KSharedMemory* shmem = info->GetSharedMemory();
+ // Initialize the info and add it to our list.
+ info->Initialize(shmem);
+ m_shared_memory_list.push_back(*info);
+ }
- while (!info->Close()) {
- shmem->Close();
- }
+ // Open a reference to the shared memory and its info.
+ shmem->Open();
+ info->Open();
- shmem->Close();
+ R_SUCCEED();
+}
- it = m_shared_memory_list.erase(it);
- KSharedMemoryInfo::Free(m_kernel, info);
+void KProcess::RemoveSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size) {
+ // Lock ourselves, to prevent concurrent access.
+ KScopedLightLock lk(m_state_lock);
+
+ // Find an existing info for the memory.
+ KSharedMemoryInfo* info = nullptr;
+ auto it = m_shared_memory_list.begin();
+ for (; it != m_shared_memory_list.end(); ++it) {
+ if (it->GetSharedMemory() == shmem) {
+ info = std::addressof(*it);
+ break;
}
}
+ ASSERT(info != nullptr);
- // Release memory to the resource limit.
- if (m_resource_limit != nullptr) {
- m_resource_limit->Close();
- m_resource_limit = nullptr;
+ // Close a reference to the info and its memory.
+ if (info->Close()) {
+ m_shared_memory_list.erase(it);
+ KSharedMemoryInfo::Free(m_kernel, info);
}
- // Finalize the page table.
- m_page_table.Finalize();
-
- // Perform inherited finalization.
- KSynchronizationObject::Finalize();
+ shmem->Close();
}
Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) {
@@ -518,7 +601,7 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) {
// See if we can get a region from a partially used TLP.
{
- KScopedSchedulerLock sl{m_kernel};
+ KScopedSchedulerLock sl(m_kernel);
if (auto it = m_partially_used_tlp_tree.begin(); it != m_partially_used_tlp_tree.end()) {
tlr = it->Reserve();
@@ -538,7 +621,9 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) {
// Allocate a new page.
tlp = KThreadLocalPage::Allocate(m_kernel);
R_UNLESS(tlp != nullptr, ResultOutOfMemory);
- auto tlp_guard = SCOPE_GUARD({ KThreadLocalPage::Free(m_kernel, tlp); });
+ ON_RESULT_FAILURE {
+ KThreadLocalPage::Free(m_kernel, tlp);
+ };
// Initialize the new page.
R_TRY(tlp->Initialize(m_kernel, this));
@@ -549,7 +634,7 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) {
// Insert into our tree.
{
- KScopedSchedulerLock sl{m_kernel};
+ KScopedSchedulerLock sl(m_kernel);
if (tlp->IsAllUsed()) {
m_fully_used_tlp_tree.insert(*tlp);
} else {
@@ -558,7 +643,6 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) {
}
// We succeeded!
- tlp_guard.Cancel();
*out = tlr;
R_SUCCEED();
}
@@ -568,7 +652,7 @@ Result KProcess::DeleteThreadLocalRegion(KProcessAddress addr) {
// Release the region.
{
- KScopedSchedulerLock sl{m_kernel};
+ KScopedSchedulerLock sl(m_kernel);
// Try to find the page in the partially used list.
auto it = m_partially_used_tlp_tree.find_key(Common::AlignDown(GetInteger(addr), PageSize));
@@ -611,95 +695,213 @@ Result KProcess::DeleteThreadLocalRegion(KProcessAddress addr) {
R_SUCCEED();
}
-bool KProcess::InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) {
- const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) {
- return wp.type == DebugWatchpointType::None;
- })};
+bool KProcess::ReserveResource(Svc::LimitableResource which, s64 value) {
+ if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) {
+ return rl->Reserve(which, value);
+ } else {
+ return true;
+ }
+}
- if (watch == m_watchpoints.end()) {
- return false;
+bool KProcess::ReserveResource(Svc::LimitableResource which, s64 value, s64 timeout) {
+ if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) {
+ return rl->Reserve(which, value, timeout);
+ } else {
+ return true;
}
+}
- watch->start_address = addr;
- watch->end_address = addr + size;
- watch->type = type;
+void KProcess::ReleaseResource(Svc::LimitableResource which, s64 value) {
+ if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) {
+ rl->Release(which, value);
+ }
+}
- for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size;
- page += PageSize) {
- m_debug_page_refcounts[page]++;
- this->GetMemory().MarkRegionDebug(page, PageSize, true);
+void KProcess::ReleaseResource(Svc::LimitableResource which, s64 value, s64 hint) {
+ if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) {
+ rl->Release(which, value, hint);
}
+}
- return true;
+void KProcess::IncrementRunningThreadCount() {
+ ASSERT(m_num_running_threads.load() >= 0);
+
+ ++m_num_running_threads;
}
-bool KProcess::RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) {
- const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) {
- return wp.start_address == addr && wp.end_address == addr + size && wp.type == type;
- })};
+void KProcess::DecrementRunningThreadCount() {
+ ASSERT(m_num_running_threads.load() > 0);
- if (watch == m_watchpoints.end()) {
+ if (const auto prev = m_num_running_threads--; prev == 1) {
+ this->Terminate();
+ }
+}
+
+bool KProcess::EnterUserException() {
+ // Get the current thread.
+ KThread* cur_thread = GetCurrentThreadPointer(m_kernel);
+ ASSERT(this == cur_thread->GetOwnerProcess());
+
+ // Check that we haven't already claimed the exception thread.
+ if (m_exception_thread == cur_thread) {
return false;
}
- watch->start_address = 0;
- watch->end_address = 0;
- watch->type = DebugWatchpointType::None;
+ // Create the wait queue we'll be using.
+ ThreadQueueImplForKProcessEnterUserException wait_queue(m_kernel,
+ std::addressof(m_exception_thread));
- for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size;
- page += PageSize) {
- m_debug_page_refcounts[page]--;
- if (!m_debug_page_refcounts[page]) {
- this->GetMemory().MarkRegionDebug(page, PageSize, false);
+ // Claim the exception thread.
+ {
+ // Lock the scheduler.
+ KScopedSchedulerLock sl(m_kernel);
+
+ // Check that we're not terminating.
+ if (cur_thread->IsTerminationRequested()) {
+ return false;
+ }
+
+ // If we don't have an exception thread, we can just claim it directly.
+ if (m_exception_thread == nullptr) {
+ m_exception_thread = cur_thread;
+ KScheduler::SetSchedulerUpdateNeeded(m_kernel);
+ return true;
}
+
+ // Otherwise, we need to wait until we don't have an exception thread.
+
+ // Add the current thread as a waiter on the current exception thread.
+ cur_thread->SetKernelAddressKey(
+ reinterpret_cast<uintptr_t>(std::addressof(m_exception_thread)) | 1);
+ m_exception_thread->AddWaiter(cur_thread);
+
+ // Wait to claim the exception thread.
+ cur_thread->BeginWait(std::addressof(wait_queue));
}
- return true;
+ // If our wait didn't end due to thread termination, we succeeded.
+ return ResultTerminationRequested != cur_thread->GetWaitResult();
}
-void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
- const auto ReprotectSegment = [&](const CodeSet::Segment& segment,
- Svc::MemoryPermission permission) {
- m_page_table.SetProcessMemoryPermission(segment.addr + base_addr, segment.size, permission);
- };
+bool KProcess::LeaveUserException() {
+ return this->ReleaseUserException(GetCurrentThreadPointer(m_kernel));
+}
- this->GetMemory().WriteBlock(base_addr, code_set.memory.data(), code_set.memory.size());
+bool KProcess::ReleaseUserException(KThread* thread) {
+ KScopedSchedulerLock sl(m_kernel);
- ReprotectSegment(code_set.CodeSegment(), Svc::MemoryPermission::ReadExecute);
- ReprotectSegment(code_set.RODataSegment(), Svc::MemoryPermission::Read);
- ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
+ if (m_exception_thread == thread) {
+ m_exception_thread = nullptr;
+
+ // Remove waiter thread.
+ bool has_waiters;
+ if (KThread* next = thread->RemoveKernelWaiterByKey(
+ std::addressof(has_waiters),
+ reinterpret_cast<uintptr_t>(std::addressof(m_exception_thread)) | 1);
+ next != nullptr) {
+ next->EndWait(ResultSuccess);
+ }
+
+ KScheduler::SetSchedulerUpdateNeeded(m_kernel);
+
+ return true;
+ } else {
+ return false;
+ }
}
-bool KProcess::IsSignaled() const {
- ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
- return m_is_signaled;
+void KProcess::RegisterThread(KThread* thread) {
+ KScopedLightLock lk(m_list_lock);
+
+ m_thread_list.push_back(*thread);
}
-KProcess::KProcess(KernelCore& kernel)
- : KAutoObjectWithSlabHeapAndContainer{kernel}, m_page_table{m_kernel.System()},
- m_handle_table{m_kernel}, m_address_arbiter{m_kernel.System()},
- m_condition_var{m_kernel.System()}, m_state_lock{m_kernel}, m_list_lock{m_kernel} {}
+void KProcess::UnregisterThread(KThread* thread) {
+ KScopedLightLock lk(m_list_lock);
-KProcess::~KProcess() = default;
+ m_thread_list.erase(m_thread_list.iterator_to(*thread));
+}
+
+size_t KProcess::GetUsedUserPhysicalMemorySize() const {
+ const size_t norm_size = m_page_table.GetNormalMemorySize();
+ const size_t other_size = m_code_size + m_main_thread_stack_size;
+ const size_t sec_size = this->GetRequiredSecureMemorySizeNonDefault();
-void KProcess::ChangeState(State new_state) {
- if (m_state == new_state) {
- return;
+ return norm_size + other_size + sec_size;
+}
+
+size_t KProcess::GetTotalUserPhysicalMemorySize() const {
+ // Get the amount of free and used size.
+ const size_t free_size =
+ m_resource_limit->GetFreeValue(Svc::LimitableResource::PhysicalMemoryMax);
+ const size_t max_size = m_max_process_memory;
+
+ // Determine used size.
+ // NOTE: This does *not* check this->IsDefaultApplicationSystemResource(), unlike
+ // GetUsedUserPhysicalMemorySize().
+ const size_t norm_size = m_page_table.GetNormalMemorySize();
+ const size_t other_size = m_code_size + m_main_thread_stack_size;
+ const size_t sec_size = this->GetRequiredSecureMemorySize();
+ const size_t used_size = norm_size + other_size + sec_size;
+
+ // NOTE: These function calls will recalculate, introducing a race...it is unclear why Nintendo
+ // does it this way.
+ if (used_size + free_size > max_size) {
+ return max_size;
+ } else {
+ return free_size + this->GetUsedUserPhysicalMemorySize();
}
+}
- m_state = new_state;
- m_is_signaled = true;
- this->NotifyAvailable();
+size_t KProcess::GetUsedNonSystemUserPhysicalMemorySize() const {
+ const size_t norm_size = m_page_table.GetNormalMemorySize();
+ const size_t other_size = m_code_size + m_main_thread_stack_size;
+
+ return norm_size + other_size;
+}
+
+size_t KProcess::GetTotalNonSystemUserPhysicalMemorySize() const {
+ // Get the amount of free and used size.
+ const size_t free_size =
+ m_resource_limit->GetFreeValue(Svc::LimitableResource::PhysicalMemoryMax);
+ const size_t max_size = m_max_process_memory;
+
+ // Determine used size.
+ // NOTE: This does *not* check this->IsDefaultApplicationSystemResource(), unlike
+ // GetUsedUserPhysicalMemorySize().
+ const size_t norm_size = m_page_table.GetNormalMemorySize();
+ const size_t other_size = m_code_size + m_main_thread_stack_size;
+ const size_t sec_size = this->GetRequiredSecureMemorySize();
+ const size_t used_size = norm_size + other_size + sec_size;
+
+ // NOTE: These function calls will recalculate, introducing a race...it is unclear why Nintendo
+ // does it this way.
+ if (used_size + free_size > max_size) {
+ return max_size - this->GetRequiredSecureMemorySizeNonDefault();
+ } else {
+ return free_size + this->GetUsedNonSystemUserPhysicalMemorySize();
+ }
}
-Result KProcess::AllocateMainThreadStack(std::size_t stack_size) {
+Result KProcess::Run(s32 priority, size_t stack_size) {
+ // Lock ourselves, to prevent concurrent access.
+ KScopedLightLock lk(m_state_lock);
+
+ // Validate that we're in a state where we can initialize.
+ const auto state = m_state;
+ R_UNLESS(state == State::Created || state == State::CreatedAttached, ResultInvalidState);
+
+ // Place a tentative reservation of a thread for this process.
+ KScopedResourceReservation thread_reservation(this, Svc::LimitableResource::ThreadCountMax);
+ R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached);
+
// Ensure that we haven't already allocated stack.
ASSERT(m_main_thread_stack_size == 0);
// Ensure that we're allocating a valid stack.
stack_size = Common::AlignUp(stack_size, PageSize);
- // R_UNLESS(stack_size + image_size <= m_max_process_memory, ResultOutOfMemory);
- R_UNLESS(stack_size + m_image_size >= m_image_size, ResultOutOfMemory);
+ R_UNLESS(stack_size + m_code_size <= m_max_process_memory, ResultOutOfMemory);
+ R_UNLESS(stack_size + m_code_size >= m_code_size, ResultOutOfMemory);
// Place a tentative reservation of memory for our new stack.
KScopedResourceReservation mem_reservation(this, Svc::LimitableResource::PhysicalMemoryMax,
@@ -707,21 +909,359 @@ Result KProcess::AllocateMainThreadStack(std::size_t stack_size) {
R_UNLESS(mem_reservation.Succeeded(), ResultLimitReached);
// Allocate and map our stack.
+ KProcessAddress stack_top = 0;
if (stack_size) {
KProcessAddress stack_bottom;
R_TRY(m_page_table.MapPages(std::addressof(stack_bottom), stack_size / PageSize,
KMemoryState::Stack, KMemoryPermission::UserReadWrite));
- m_main_thread_stack_top = stack_bottom + stack_size;
+ stack_top = stack_bottom + stack_size;
m_main_thread_stack_size = stack_size;
}
+ // Ensure our stack is safe to clean up on exit.
+ ON_RESULT_FAILURE {
+ if (m_main_thread_stack_size) {
+ ASSERT(R_SUCCEEDED(m_page_table.UnmapPages(stack_top - m_main_thread_stack_size,
+ m_main_thread_stack_size / PageSize,
+ KMemoryState::Stack)));
+ m_main_thread_stack_size = 0;
+ }
+ };
+
+ // Set our maximum heap size.
+ R_TRY(m_page_table.SetMaxHeapSize(m_max_process_memory -
+ (m_main_thread_stack_size + m_code_size)));
+
+ // Initialize our handle table.
+ R_TRY(this->InitializeHandleTable(m_capabilities.GetHandleTableSize()));
+ ON_RESULT_FAILURE_2 {
+ this->FinalizeHandleTable();
+ };
+
+ // Create a new thread for the process.
+ KThread* main_thread = KThread::Create(m_kernel);
+ R_UNLESS(main_thread != nullptr, ResultOutOfResource);
+ SCOPE_EXIT({ main_thread->Close(); });
+
+ // Initialize the thread.
+ R_TRY(KThread::InitializeUserThread(m_kernel.System(), main_thread, this->GetEntryPoint(), 0,
+ stack_top, priority, m_ideal_core_id, this));
+
+ // Register the thread, and commit our reservation.
+ KThread::Register(m_kernel, main_thread);
+ thread_reservation.Commit();
+
+ // Add the thread to our handle table.
+ Handle thread_handle;
+ R_TRY(m_handle_table.Add(std::addressof(thread_handle), main_thread));
+
+ // Set the thread arguments.
+ main_thread->GetContext32().cpu_registers[0] = 0;
+ main_thread->GetContext64().cpu_registers[0] = 0;
+ main_thread->GetContext32().cpu_registers[1] = thread_handle;
+ main_thread->GetContext64().cpu_registers[1] = thread_handle;
+
+ // Update our state.
+ this->ChangeState((state == State::Created) ? State::Running : State::RunningAttached);
+ ON_RESULT_FAILURE_2 {
+ this->ChangeState(state);
+ };
+
+ // Suspend for debug, if we should.
+ if (m_kernel.System().DebuggerEnabled()) {
+ main_thread->RequestSuspend(SuspendType::Debug);
+ }
+
+ // Run our thread.
+ R_TRY(main_thread->Run());
+
+ // Open a reference to represent that we're running.
+ this->Open();
+
// We succeeded! Commit our memory reservation.
mem_reservation.Commit();
R_SUCCEED();
}
+Result KProcess::Reset() {
+ // Lock the process and the scheduler.
+ KScopedLightLock lk(m_state_lock);
+ KScopedSchedulerLock sl(m_kernel);
+
+ // Validate that we're in a state that we can reset.
+ R_UNLESS(m_state != State::Terminated, ResultInvalidState);
+ R_UNLESS(m_is_signaled, ResultInvalidState);
+
+ // Clear signaled.
+ m_is_signaled = false;
+ R_SUCCEED();
+}
+
+Result KProcess::SetActivity(Svc::ProcessActivity activity) {
+ // Lock ourselves and the scheduler.
+ KScopedLightLock lk(m_state_lock);
+ KScopedLightLock list_lk(m_list_lock);
+ KScopedSchedulerLock sl(m_kernel);
+
+ // Validate our state.
+ R_UNLESS(m_state != State::Terminating, ResultInvalidState);
+ R_UNLESS(m_state != State::Terminated, ResultInvalidState);
+
+ // Either pause or resume.
+ if (activity == Svc::ProcessActivity::Paused) {
+ // Verify that we're not suspended.
+ R_UNLESS(!m_is_suspended, ResultInvalidState);
+
+ // Suspend all threads.
+ auto end = this->GetThreadList().end();
+ for (auto it = this->GetThreadList().begin(); it != end; ++it) {
+ it->RequestSuspend(SuspendType::Process);
+ }
+
+ // Set ourselves as suspended.
+ this->SetSuspended(true);
+ } else {
+ ASSERT(activity == Svc::ProcessActivity::Runnable);
+
+ // Verify that we're suspended.
+ R_UNLESS(m_is_suspended, ResultInvalidState);
+
+ // Resume all threads.
+ auto end = this->GetThreadList().end();
+ for (auto it = this->GetThreadList().begin(); it != end; ++it) {
+ it->Resume(SuspendType::Process);
+ }
+
+ // Set ourselves as resumed.
+ this->SetSuspended(false);
+ }
+
+ R_SUCCEED();
+}
+
+void KProcess::PinCurrentThread() {
+ ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
+
+ // Get the current thread.
+ const s32 core_id = GetCurrentCoreId(m_kernel);
+ KThread* cur_thread = GetCurrentThreadPointer(m_kernel);
+
+ // If the thread isn't terminated, pin it.
+ if (!cur_thread->IsTerminationRequested()) {
+ // Pin it.
+ this->PinThread(core_id, cur_thread);
+ cur_thread->Pin(core_id);
+
+ // An update is needed.
+ KScheduler::SetSchedulerUpdateNeeded(m_kernel);
+ }
+}
+
+void KProcess::UnpinCurrentThread() {
+ ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
+
+ // Get the current thread.
+ const s32 core_id = GetCurrentCoreId(m_kernel);
+ KThread* cur_thread = GetCurrentThreadPointer(m_kernel);
+
+ // Unpin it.
+ cur_thread->Unpin();
+ this->UnpinThread(core_id, cur_thread);
+
+ // An update is needed.
+ KScheduler::SetSchedulerUpdateNeeded(m_kernel);
+}
+
+void KProcess::UnpinThread(KThread* thread) {
+ ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
+
+ // Get the thread's core id.
+ const auto core_id = thread->GetActiveCore();
+
+ // Unpin it.
+ this->UnpinThread(core_id, thread);
+ thread->Unpin();
+
+ // An update is needed.
+ KScheduler::SetSchedulerUpdateNeeded(m_kernel);
+}
+
+Result KProcess::GetThreadList(s32* out_num_threads, KProcessAddress out_thread_ids,
+ s32 max_out_count) {
+ // TODO: use current memory reference
+ auto& memory = m_kernel.System().ApplicationMemory();
+
+ // Lock the list.
+ KScopedLightLock lk(m_list_lock);
+
+ // Iterate over the list.
+ s32 count = 0;
+ auto end = this->GetThreadList().end();
+ for (auto it = this->GetThreadList().begin(); it != end; ++it) {
+ // If we're within array bounds, write the id.
+ if (count < max_out_count) {
+ // Get the thread id.
+ KThread* thread = std::addressof(*it);
+ const u64 id = thread->GetId();
+
+ // Copy the id to userland.
+ memory.Write64(out_thread_ids + count * sizeof(u64), id);
+ }
+
+ // Increment the count.
+ ++count;
+ }
+
+ // We successfully iterated the list.
+ *out_num_threads = count;
+ R_SUCCEED();
+}
+
+void KProcess::Switch(KProcess* cur_process, KProcess* next_process) {}
+
+KProcess::KProcess(KernelCore& kernel)
+ : KAutoObjectWithSlabHeapAndContainer(kernel), m_page_table{kernel.System()},
+ m_state_lock{kernel}, m_list_lock{kernel}, m_cond_var{kernel.System()},
+ m_address_arbiter{kernel.System()}, m_handle_table{kernel} {}
+KProcess::~KProcess() = default;
+
+Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
+ bool is_hbl) {
+ // Create a resource limit for the process.
+ const auto physical_memory_size =
+ m_kernel.MemoryManager().GetSize(Kernel::KMemoryManager::Pool::Application);
+ auto* res_limit =
+ Kernel::CreateResourceLimitForProcess(m_kernel.System(), physical_memory_size);
+
+ // Ensure we maintain a clean state on exit.
+ SCOPE_EXIT({ res_limit->Close(); });
+
+ // Declare flags and code address.
+ Svc::CreateProcessFlag flag{};
+ u64 code_address{};
+
+ // We are an application.
+ flag |= Svc::CreateProcessFlag::IsApplication;
+
+ // If we are 64-bit, create as such.
+ if (metadata.Is64BitProgram()) {
+ flag |= Svc::CreateProcessFlag::Is64Bit;
+ }
+
+ // Set the address space type and code address.
+ switch (metadata.GetAddressSpaceType()) {
+ case FileSys::ProgramAddressSpaceType::Is39Bit:
+ flag |= Svc::CreateProcessFlag::AddressSpace64Bit;
+
+ // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large.
+ // However, some (buggy) programs/libraries like skyline incorrectly depend on the
+ // existence of ASLR pages before the entry point, so we will adjust the load address
+ // to point to about 2GiB into the ASLR region.
+ code_address = 0x8000'0000;
+ break;
+ case FileSys::ProgramAddressSpaceType::Is36Bit:
+ flag |= Svc::CreateProcessFlag::AddressSpace64BitDeprecated;
+ code_address = 0x800'0000;
+ break;
+ case FileSys::ProgramAddressSpaceType::Is32Bit:
+ flag |= Svc::CreateProcessFlag::AddressSpace32Bit;
+ code_address = 0x20'0000;
+ break;
+ case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
+ flag |= Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias;
+ code_address = 0x20'0000;
+ break;
+ }
+
+ Svc::CreateProcessParameter params{
+ .name = {},
+ .version = {},
+ .program_id = metadata.GetTitleID(),
+ .code_address = code_address,
+ .code_num_pages = static_cast<s32>(code_size / PageSize),
+ .flags = flag,
+ .reslimit = Svc::InvalidHandle,
+ .system_resource_num_pages = static_cast<s32>(metadata.GetSystemResourceSize() / PageSize),
+ };
+
+ // Set the process name.
+ const auto& name = metadata.GetName();
+ static_assert(sizeof(params.name) <= sizeof(name));
+ std::memcpy(params.name.data(), name.data(), sizeof(params.name));
+
+ // Initialize for application process.
+ R_TRY(this->Initialize(params, metadata.GetKernelCapabilities(), res_limit,
+ KMemoryManager::Pool::Application));
+
+ // Assign remaining properties.
+ m_is_hbl = is_hbl;
+ m_ideal_core_id = metadata.GetMainThreadCore();
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
+ const auto ReprotectSegment = [&](const CodeSet::Segment& segment,
+ Svc::MemoryPermission permission) {
+ m_page_table.SetProcessMemoryPermission(segment.addr + base_addr, segment.size, permission);
+ };
+
+ this->GetMemory().WriteBlock(base_addr, code_set.memory.data(), code_set.memory.size());
+
+ ReprotectSegment(code_set.CodeSegment(), Svc::MemoryPermission::ReadExecute);
+ ReprotectSegment(code_set.RODataSegment(), Svc::MemoryPermission::Read);
+ ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
+}
+
+bool KProcess::InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) {
+ const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) {
+ return wp.type == DebugWatchpointType::None;
+ })};
+
+ if (watch == m_watchpoints.end()) {
+ return false;
+ }
+
+ watch->start_address = addr;
+ watch->end_address = addr + size;
+ watch->type = type;
+
+ for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size;
+ page += PageSize) {
+ m_debug_page_refcounts[page]++;
+ this->GetMemory().MarkRegionDebug(page, PageSize, true);
+ }
+
+ return true;
+}
+
+bool KProcess::RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) {
+ const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) {
+ return wp.start_address == addr && wp.end_address == addr + size && wp.type == type;
+ })};
+
+ if (watch == m_watchpoints.end()) {
+ return false;
+ }
+
+ watch->start_address = 0;
+ watch->end_address = 0;
+ watch->type = DebugWatchpointType::None;
+
+ for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size;
+ page += PageSize) {
+ m_debug_page_refcounts[page]--;
+ if (!m_debug_page_refcounts[page]) {
+ this->GetMemory().MarkRegionDebug(page, PageSize, false);
+ }
+ }
+
+ return true;
+}
+
Core::Memory::Memory& KProcess::GetMemory() const {
// TODO: per-process memory
return m_kernel.System().ApplicationMemory();
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h
index 146e07a57..f9f755afa 100644
--- a/src/core/hle/kernel/k_process.h
+++ b/src/core/hle/kernel/k_process.h
@@ -1,59 +1,23 @@
-// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <array>
-#include <cstddef>
-#include <list>
#include <map>
-#include <string>
+
+#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/k_address_arbiter.h"
-#include "core/hle/kernel/k_auto_object.h"
+#include "core/hle/kernel/k_capabilities.h"
#include "core/hle/kernel/k_condition_variable.h"
#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/k_page_table.h"
-#include "core/hle/kernel/k_synchronization_object.h"
+#include "core/hle/kernel/k_page_table_manager.h"
+#include "core/hle/kernel/k_system_resource.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_thread_local_page.h"
-#include "core/hle/kernel/k_typed_address.h"
-#include "core/hle/kernel/k_worker_task.h"
-#include "core/hle/kernel/process_capability.h"
-#include "core/hle/kernel/slab_helpers.h"
-#include "core/hle/result.h"
-
-namespace Core {
-namespace Memory {
-class Memory;
-};
-
-class System;
-} // namespace Core
-
-namespace FileSys {
-class ProgramMetadata;
-}
namespace Kernel {
-class KernelCore;
-class KResourceLimit;
-class KThread;
-class KSharedMemoryInfo;
-class TLSPage;
-
-struct CodeSet;
-
-enum class MemoryRegion : u16 {
- APPLICATION = 1,
- SYSTEM = 2,
- BASE = 3,
-};
-
-enum class ProcessActivity : u32 {
- Runnable,
- Paused,
-};
-
enum class DebugWatchpointType : u8 {
None = 0,
Read = 1 << 0,
@@ -72,9 +36,6 @@ class KProcess final : public KAutoObjectWithSlabHeapAndContainer<KProcess, KWor
KERNEL_AUTOOBJECT_TRAITS(KProcess, KSynchronizationObject);
public:
- explicit KProcess(KernelCore& kernel);
- ~KProcess() override;
-
enum class State {
Created = static_cast<u32>(Svc::ProcessState::Created),
CreatedAttached = static_cast<u32>(Svc::ProcessState::CreatedAttached),
@@ -86,470 +47,493 @@ public:
DebugBreak = static_cast<u32>(Svc::ProcessState::DebugBreak),
};
- enum : u64 {
- /// Lowest allowed process ID for a kernel initial process.
- InitialKIPIDMin = 1,
- /// Highest allowed process ID for a kernel initial process.
- InitialKIPIDMax = 80,
-
- /// Lowest allowed process ID for a userland process.
- ProcessIDMin = 81,
- /// Highest allowed process ID for a userland process.
- ProcessIDMax = 0xFFFFFFFFFFFFFFFF,
- };
+ using ThreadList = Common::IntrusiveListMemberTraits<&KThread::m_process_list_node>::ListType;
- // Used to determine how process IDs are assigned.
- enum class ProcessType {
- KernelInternal,
- Userland,
- };
+ static constexpr size_t AslrAlignment = 2_MiB;
- static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4;
+public:
+ static constexpr u64 InitialProcessIdMin = 1;
+ static constexpr u64 InitialProcessIdMax = 0x50;
- static Result Initialize(KProcess* process, Core::System& system, std::string process_name,
- ProcessType type, KResourceLimit* res_limit);
+ static constexpr u64 ProcessIdMin = InitialProcessIdMax + 1;
+ static constexpr u64 ProcessIdMax = std::numeric_limits<u64>::max();
- /// Gets a reference to the process' page table.
- KPageTable& GetPageTable() {
- return m_page_table;
- }
+private:
+ using SharedMemoryInfoList = Common::IntrusiveListBaseTraits<KSharedMemoryInfo>::ListType;
+ using TLPTree =
+ Common::IntrusiveRedBlackTreeBaseTraits<KThreadLocalPage>::TreeType<KThreadLocalPage>;
+ using TLPIterator = TLPTree::iterator;
- /// Gets const a reference to the process' page table.
- const KPageTable& GetPageTable() const {
- return m_page_table;
- }
+private:
+ KPageTable m_page_table;
+ std::atomic<size_t> m_used_kernel_memory_size{};
+ TLPTree m_fully_used_tlp_tree{};
+ TLPTree m_partially_used_tlp_tree{};
+ s32 m_ideal_core_id{};
+ KResourceLimit* m_resource_limit{};
+ KSystemResource* m_system_resource{};
+ size_t m_memory_release_hint{};
+ State m_state{};
+ KLightLock m_state_lock;
+ KLightLock m_list_lock;
+ KConditionVariable m_cond_var;
+ KAddressArbiter m_address_arbiter;
+ std::array<u64, 4> m_entropy{};
+ bool m_is_signaled{};
+ bool m_is_initialized{};
+ bool m_is_application{};
+ bool m_is_default_application_system_resource{};
+ bool m_is_hbl{};
+ std::array<char, 13> m_name{};
+ std::atomic<u16> m_num_running_threads{};
+ Svc::CreateProcessFlag m_flags{};
+ KMemoryManager::Pool m_memory_pool{};
+ s64 m_schedule_count{};
+ KCapabilities m_capabilities{};
+ u64 m_program_id{};
+ u64 m_process_id{};
+ KProcessAddress m_code_address{};
+ size_t m_code_size{};
+ size_t m_main_thread_stack_size{};
+ size_t m_max_process_memory{};
+ u32 m_version{};
+ KHandleTable m_handle_table;
+ KProcessAddress m_plr_address{};
+ KThread* m_exception_thread{};
+ ThreadList m_thread_list{};
+ SharedMemoryInfoList m_shared_memory_list{};
+ bool m_is_suspended{};
+ bool m_is_immortal{};
+ bool m_is_handle_table_initialized{};
+ std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_running_threads{};
+ std::array<u64, Core::Hardware::NUM_CPU_CORES> m_running_thread_idle_counts{};
+ std::array<u64, Core::Hardware::NUM_CPU_CORES> m_running_thread_switch_counts{};
+ std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_pinned_threads{};
+ std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> m_watchpoints{};
+ std::map<KProcessAddress, u64> m_debug_page_refcounts{};
+ std::atomic<s64> m_cpu_time{};
+ std::atomic<s64> m_num_process_switches{};
+ std::atomic<s64> m_num_thread_switches{};
+ std::atomic<s64> m_num_fpu_switches{};
+ std::atomic<s64> m_num_supervisor_calls{};
+ std::atomic<s64> m_num_ipc_messages{};
+ std::atomic<s64> m_num_ipc_replies{};
+ std::atomic<s64> m_num_ipc_receives{};
- /// Gets a reference to the process' handle table.
- KHandleTable& GetHandleTable() {
- return m_handle_table;
- }
+private:
+ Result StartTermination();
+ void FinishTermination();
- /// Gets a const reference to the process' handle table.
- const KHandleTable& GetHandleTable() const {
- return m_handle_table;
+ void PinThread(s32 core_id, KThread* thread) {
+ ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
+ ASSERT(thread != nullptr);
+ ASSERT(m_pinned_threads[core_id] == nullptr);
+ m_pinned_threads[core_id] = thread;
}
- /// Gets a reference to process's memory.
- Core::Memory::Memory& GetMemory() const;
-
- Result SignalToAddress(KProcessAddress address) {
- return m_condition_var.SignalToAddress(address);
+ void UnpinThread(s32 core_id, KThread* thread) {
+ ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
+ ASSERT(thread != nullptr);
+ ASSERT(m_pinned_threads[core_id] == thread);
+ m_pinned_threads[core_id] = nullptr;
}
- Result WaitForAddress(Handle handle, KProcessAddress address, u32 tag) {
- return m_condition_var.WaitForAddress(handle, address, tag);
- }
+public:
+ explicit KProcess(KernelCore& kernel);
+ ~KProcess() override;
- void SignalConditionVariable(u64 cv_key, int32_t count) {
- return m_condition_var.Signal(cv_key, count);
- }
+ Result Initialize(const Svc::CreateProcessParameter& params, KResourceLimit* res_limit,
+ bool is_real);
- Result WaitConditionVariable(KProcessAddress address, u64 cv_key, u32 tag, s64 ns) {
- R_RETURN(m_condition_var.Wait(address, cv_key, tag, ns));
- }
+ Result Initialize(const Svc::CreateProcessParameter& params, const KPageGroup& pg,
+ std::span<const u32> caps, KResourceLimit* res_limit,
+ KMemoryManager::Pool pool, bool immortal);
+ Result Initialize(const Svc::CreateProcessParameter& params, std::span<const u32> user_caps,
+ KResourceLimit* res_limit, KMemoryManager::Pool pool);
+ void Exit();
- Result SignalAddressArbiter(uint64_t address, Svc::SignalType signal_type, s32 value,
- s32 count) {
- R_RETURN(m_address_arbiter.SignalToAddress(address, signal_type, value, count));
+ const char* GetName() const {
+ return m_name.data();
}
- Result WaitAddressArbiter(uint64_t address, Svc::ArbitrationType arb_type, s32 value,
- s64 timeout) {
- R_RETURN(m_address_arbiter.WaitForAddress(address, arb_type, value, timeout));
+ u64 GetProgramId() const {
+ return m_program_id;
}
- KProcessAddress GetProcessLocalRegionAddress() const {
- return m_plr_address;
+ u64 GetProcessId() const {
+ return m_process_id;
}
- /// Gets the current status of the process
State GetState() const {
return m_state;
}
- /// Gets the unique ID that identifies this particular process.
- u64 GetProcessId() const {
- return m_process_id;
+ u64 GetCoreMask() const {
+ return m_capabilities.GetCoreMask();
+ }
+ u64 GetPhysicalCoreMask() const {
+ return m_capabilities.GetPhysicalCoreMask();
+ }
+ u64 GetPriorityMask() const {
+ return m_capabilities.GetPriorityMask();
}
- /// Gets the program ID corresponding to this process.
- u64 GetProgramId() const {
- return m_program_id;
+ s32 GetIdealCoreId() const {
+ return m_ideal_core_id;
+ }
+ void SetIdealCoreId(s32 core_id) {
+ m_ideal_core_id = core_id;
}
- KProcessAddress GetEntryPoint() const {
- return m_code_address;
+ bool CheckThreadPriority(s32 prio) const {
+ return ((1ULL << prio) & this->GetPriorityMask()) != 0;
}
- /// Gets the resource limit descriptor for this process
- KResourceLimit* GetResourceLimit() const;
+ u32 GetCreateProcessFlags() const {
+ return static_cast<u32>(m_flags);
+ }
- /// Gets the ideal CPU core ID for this process
- u8 GetIdealCoreId() const {
- return m_ideal_core;
+ bool Is64Bit() const {
+ return True(m_flags & Svc::CreateProcessFlag::Is64Bit);
}
- /// Checks if the specified thread priority is valid.
- bool CheckThreadPriority(s32 prio) const {
- return ((1ULL << prio) & GetPriorityMask()) != 0;
+ KProcessAddress GetEntryPoint() const {
+ return m_code_address;
}
- /// Gets the bitmask of allowed cores that this process' threads can run on.
- u64 GetCoreMask() const {
- return m_capabilities.GetCoreMask();
+ size_t GetMainStackSize() const {
+ return m_main_thread_stack_size;
}
- /// Gets the bitmask of allowed thread priorities.
- u64 GetPriorityMask() const {
- return m_capabilities.GetPriorityMask();
+ KMemoryManager::Pool GetMemoryPool() const {
+ return m_memory_pool;
}
- /// Gets the amount of secure memory to allocate for memory management.
- u32 GetSystemResourceSize() const {
- return m_system_resource_size;
+ u64 GetRandomEntropy(size_t i) const {
+ return m_entropy[i];
}
- /// Gets the amount of secure memory currently in use for memory management.
- u32 GetSystemResourceUsage() const {
- // On hardware, this returns the amount of system resource memory that has
- // been used by the kernel. This is problematic for Yuzu to emulate, because
- // system resource memory is used for page tables -- and yuzu doesn't really
- // have a way to calculate how much memory is required for page tables for
- // the current process at any given time.
- // TODO: Is this even worth implementing? Games may retrieve this value via
- // an SDK function that gets used + available system resource size for debug
- // or diagnostic purposes. However, it seems unlikely that a game would make
- // decisions based on how much system memory is dedicated to its page tables.
- // Is returning a value other than zero wise?
- return 0;
+ bool IsApplication() const {
+ return m_is_application;
}
- /// Whether this process is an AArch64 or AArch32 process.
- bool Is64BitProcess() const {
- return m_is_64bit_process;
+ bool IsDefaultApplicationSystemResource() const {
+ return m_is_default_application_system_resource;
}
bool IsSuspended() const {
return m_is_suspended;
}
-
void SetSuspended(bool suspended) {
m_is_suspended = suspended;
}
- /// Gets the total running time of the process instance in ticks.
- u64 GetCPUTimeTicks() const {
- return m_total_process_running_time_ticks;
+ Result Terminate();
+
+ bool IsTerminated() const {
+ return m_state == State::Terminated;
}
- /// Updates the total running time, adding the given ticks to it.
- void UpdateCPUTimeTicks(u64 ticks) {
- m_total_process_running_time_ticks += ticks;
+ bool IsPermittedSvc(u32 svc_id) const {
+ return m_capabilities.IsPermittedSvc(svc_id);
}
- /// Gets the process schedule count, used for thread yielding
- s64 GetScheduledCount() const {
- return m_schedule_count;
+ bool IsPermittedInterrupt(s32 interrupt_id) const {
+ return m_capabilities.IsPermittedInterrupt(interrupt_id);
}
- /// Increments the process schedule count, used for thread yielding.
- void IncrementScheduledCount() {
- ++m_schedule_count;
+ bool IsPermittedDebug() const {
+ return m_capabilities.IsPermittedDebug();
}
- void IncrementRunningThreadCount();
- void DecrementRunningThreadCount();
+ bool CanForceDebug() const {
+ return m_capabilities.CanForceDebug();
+ }
- void SetRunningThread(s32 core, KThread* thread, u64 idle_count) {
- m_running_threads[core] = thread;
- m_running_thread_idle_counts[core] = idle_count;
+ bool IsHbl() const {
+ return m_is_hbl;
}
- void ClearRunningThread(KThread* thread) {
- for (size_t i = 0; i < m_running_threads.size(); ++i) {
- if (m_running_threads[i] == thread) {
- m_running_threads[i] = nullptr;
- }
- }
+ Kernel::KMemoryManager::Direction GetAllocateOption() const {
+ // TODO: property of the KPageTableBase
+ return KMemoryManager::Direction::FromFront;
}
- [[nodiscard]] KThread* GetRunningThread(s32 core) const {
- return m_running_threads[core];
+ ThreadList& GetThreadList() {
+ return m_thread_list;
+ }
+ const ThreadList& GetThreadList() const {
+ return m_thread_list;
}
+ bool EnterUserException();
+ bool LeaveUserException();
bool ReleaseUserException(KThread* thread);
- [[nodiscard]] KThread* GetPinnedThread(s32 core_id) const {
+ KThread* GetPinnedThread(s32 core_id) const {
ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
return m_pinned_threads[core_id];
}
- /// Gets 8 bytes of random data for svcGetInfo RandomEntropy
- u64 GetRandomEntropy(std::size_t index) const {
- return m_random_entropy.at(index);
+ const Svc::SvcAccessFlagSet& GetSvcPermissions() const {
+ return m_capabilities.GetSvcPermissions();
}
- /// Retrieves the total physical memory available to this process in bytes.
- u64 GetTotalPhysicalMemoryAvailable();
-
- /// Retrieves the total physical memory available to this process in bytes,
- /// without the size of the personal system resource heap added to it.
- u64 GetTotalPhysicalMemoryAvailableWithoutSystemResource();
-
- /// Retrieves the total physical memory used by this process in bytes.
- u64 GetTotalPhysicalMemoryUsed();
-
- /// Retrieves the total physical memory used by this process in bytes,
- /// without the size of the personal system resource heap added to it.
- u64 GetTotalPhysicalMemoryUsedWithoutSystemResource();
-
- /// Gets the list of all threads created with this process as their owner.
- std::list<KThread*>& GetThreadList() {
- return m_thread_list;
+ KResourceLimit* GetResourceLimit() const {
+ return m_resource_limit;
}
- /// Registers a thread as being created under this process,
- /// adding it to this process' thread list.
- void RegisterThread(KThread* thread);
+ bool ReserveResource(Svc::LimitableResource which, s64 value);
+ bool ReserveResource(Svc::LimitableResource which, s64 value, s64 timeout);
+ void ReleaseResource(Svc::LimitableResource which, s64 value);
+ void ReleaseResource(Svc::LimitableResource which, s64 value, s64 hint);
- /// Unregisters a thread from this process, removing it
- /// from this process' thread list.
- void UnregisterThread(KThread* thread);
+ KLightLock& GetStateLock() {
+ return m_state_lock;
+ }
+ KLightLock& GetListLock() {
+ return m_list_lock;
+ }
- /// Retrieves the number of available threads for this process.
- u64 GetFreeThreadCount() const;
-
- /// Clears the signaled state of the process if and only if it's signaled.
- ///
- /// @pre The process must not be already terminated. If this is called on a
- /// terminated process, then ResultInvalidState will be returned.
- ///
- /// @pre The process must be in a signaled state. If this is called on a
- /// process instance that is not signaled, ResultInvalidState will be
- /// returned.
- Result Reset();
+ KPageTable& GetPageTable() {
+ return m_page_table;
+ }
+ const KPageTable& GetPageTable() const {
+ return m_page_table;
+ }
- /**
- * Loads process-specifics configuration info with metadata provided
- * by an executable.
- *
- * @param metadata The provided metadata to load process specific info from.
- *
- * @returns ResultSuccess if all relevant metadata was able to be
- * loaded and parsed. Otherwise, an error code is returned.
- */
- Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
- bool is_hbl);
+ KHandleTable& GetHandleTable() {
+ return m_handle_table;
+ }
+ const KHandleTable& GetHandleTable() const {
+ return m_handle_table;
+ }
- /**
- * Starts the main application thread for this process.
- *
- * @param main_thread_priority The priority for the main thread.
- * @param stack_size The stack size for the main thread in bytes.
- */
- void Run(s32 main_thread_priority, u64 stack_size);
+ size_t GetUsedUserPhysicalMemorySize() const;
+ size_t GetTotalUserPhysicalMemorySize() const;
+ size_t GetUsedNonSystemUserPhysicalMemorySize() const;
+ size_t GetTotalNonSystemUserPhysicalMemorySize() const;
- /**
- * Prepares a process for termination by stopping all of its threads
- * and clearing any other resources.
- */
- void PrepareForTermination();
+ Result AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size);
+ void RemoveSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size);
- void LoadModule(CodeSet code_set, KProcessAddress base_addr);
+ Result CreateThreadLocalRegion(KProcessAddress* out);
+ Result DeleteThreadLocalRegion(KProcessAddress addr);
- bool IsInitialized() const override {
- return m_is_initialized;
+ KProcessAddress GetProcessLocalRegionAddress() const {
+ return m_plr_address;
}
- static void PostDestroy(uintptr_t arg) {}
-
- void Finalize() override;
-
- u64 GetId() const override {
- return GetProcessId();
+ KThread* GetExceptionThread() const {
+ return m_exception_thread;
}
- bool IsHbl() const {
- return m_is_hbl;
+ void AddCpuTime(s64 diff) {
+ m_cpu_time += diff;
+ }
+ s64 GetCpuTime() {
+ return m_cpu_time.load();
}
- bool IsSignaled() const override;
-
- void DoWorkerTaskImpl();
+ s64 GetScheduledCount() const {
+ return m_schedule_count;
+ }
+ void IncrementScheduledCount() {
+ ++m_schedule_count;
+ }
- Result SetActivity(ProcessActivity activity);
+ void IncrementRunningThreadCount();
+ void DecrementRunningThreadCount();
- void PinCurrentThread(s32 core_id);
- void UnpinCurrentThread(s32 core_id);
- void UnpinThread(KThread* thread);
+ size_t GetRequiredSecureMemorySizeNonDefault() const {
+ if (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) {
+ auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource);
+ return secure_system_resource->CalculateRequiredSecureMemorySize();
+ }
- KLightLock& GetStateLock() {
- return m_state_lock;
+ return 0;
}
- Result AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size);
- void RemoveSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size);
-
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // Thread-local storage management
-
- // Marks the next available region as used and returns the address of the slot.
- [[nodiscard]] Result CreateThreadLocalRegion(KProcessAddress* out);
+ size_t GetRequiredSecureMemorySize() const {
+ if (m_system_resource->IsSecureResource()) {
+ auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource);
+ return secure_system_resource->CalculateRequiredSecureMemorySize();
+ }
- // Frees a used TLS slot identified by the given address
- Result DeleteThreadLocalRegion(KProcessAddress addr);
+ return 0;
+ }
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // Debug watchpoint management
+ size_t GetTotalSystemResourceSize() const {
+ if (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) {
+ auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource);
+ return secure_system_resource->GetSize();
+ }
- // Attempts to insert a watchpoint into a free slot. Returns false if none are available.
- bool InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type);
+ return 0;
+ }
- // Attempts to remove the watchpoint specified by the given parameters.
- bool RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type);
+ size_t GetUsedSystemResourceSize() const {
+ if (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) {
+ auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource);
+ return secure_system_resource->GetUsedSize();
+ }
- const std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>& GetWatchpoints() const {
- return m_watchpoints;
+ return 0;
}
- const std::string& GetName() {
- return name;
+ void SetRunningThread(s32 core, KThread* thread, u64 idle_count, u64 switch_count) {
+ m_running_threads[core] = thread;
+ m_running_thread_idle_counts[core] = idle_count;
+ m_running_thread_switch_counts[core] = switch_count;
}
-private:
- void PinThread(s32 core_id, KThread* thread) {
- ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
- ASSERT(thread != nullptr);
- ASSERT(m_pinned_threads[core_id] == nullptr);
- m_pinned_threads[core_id] = thread;
+ void ClearRunningThread(KThread* thread) {
+ for (size_t i = 0; i < m_running_threads.size(); ++i) {
+ if (m_running_threads[i] == thread) {
+ m_running_threads[i] = nullptr;
+ }
+ }
}
- void UnpinThread(s32 core_id, KThread* thread) {
- ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
- ASSERT(thread != nullptr);
- ASSERT(m_pinned_threads[core_id] == thread);
- m_pinned_threads[core_id] = nullptr;
+ const KSystemResource& GetSystemResource() const {
+ return *m_system_resource;
}
- void FinalizeHandleTable() {
- // Finalize the table.
- m_handle_table.Finalize();
-
- // Note that the table is finalized.
- m_is_handle_table_initialized = false;
+ const KMemoryBlockSlabManager& GetMemoryBlockSlabManager() const {
+ return m_system_resource->GetMemoryBlockSlabManager();
+ }
+ const KBlockInfoManager& GetBlockInfoManager() const {
+ return m_system_resource->GetBlockInfoManager();
+ }
+ const KPageTableManager& GetPageTableManager() const {
+ return m_system_resource->GetPageTableManager();
}
- void ChangeState(State new_state);
-
- /// Allocates the main thread stack for the process, given the stack size in bytes.
- Result AllocateMainThreadStack(std::size_t stack_size);
-
- /// Memory manager for this process
- KPageTable m_page_table;
-
- /// Current status of the process
- State m_state{};
+ KThread* GetRunningThread(s32 core) const {
+ return m_running_threads[core];
+ }
+ u64 GetRunningThreadIdleCount(s32 core) const {
+ return m_running_thread_idle_counts[core];
+ }
+ u64 GetRunningThreadSwitchCount(s32 core) const {
+ return m_running_thread_switch_counts[core];
+ }
- /// The ID of this process
- u64 m_process_id = 0;
+ void RegisterThread(KThread* thread);
+ void UnregisterThread(KThread* thread);
- /// Title ID corresponding to the process
- u64 m_program_id = 0;
+ Result Run(s32 priority, size_t stack_size);
- /// Specifies additional memory to be reserved for the process's memory management by the
- /// system. When this is non-zero, secure memory is allocated and used for page table allocation
- /// instead of using the normal global page tables/memory block management.
- u32 m_system_resource_size = 0;
+ Result Reset();
- /// Resource limit descriptor for this process
- KResourceLimit* m_resource_limit{};
+ void SetDebugBreak() {
+ if (m_state == State::RunningAttached) {
+ this->ChangeState(State::DebugBreak);
+ }
+ }
- KVirtualAddress m_system_resource_address{};
+ void SetAttached() {
+ if (m_state == State::DebugBreak) {
+ this->ChangeState(State::RunningAttached);
+ }
+ }
- /// The ideal CPU core for this process, threads are scheduled on this core by default.
- u8 m_ideal_core = 0;
+ Result SetActivity(Svc::ProcessActivity activity);
- /// Contains the parsed process capability descriptors.
- ProcessCapabilities m_capabilities;
+ void PinCurrentThread();
+ void UnpinCurrentThread();
+ void UnpinThread(KThread* thread);
- /// Whether or not this process is AArch64, or AArch32.
- /// By default, we currently assume this is true, unless otherwise
- /// specified by metadata provided to the process during loading.
- bool m_is_64bit_process = true;
+ void SignalConditionVariable(uintptr_t cv_key, int32_t count) {
+ return m_cond_var.Signal(cv_key, count);
+ }
- /// Total running time for the process in ticks.
- std::atomic<u64> m_total_process_running_time_ticks = 0;
+ Result WaitConditionVariable(KProcessAddress address, uintptr_t cv_key, u32 tag, s64 ns) {
+ R_RETURN(m_cond_var.Wait(address, cv_key, tag, ns));
+ }
- /// Per-process handle table for storing created object handles in.
- KHandleTable m_handle_table;
+ Result SignalAddressArbiter(uintptr_t address, Svc::SignalType signal_type, s32 value,
+ s32 count) {
+ R_RETURN(m_address_arbiter.SignalToAddress(address, signal_type, value, count));
+ }
- /// Per-process address arbiter.
- KAddressArbiter m_address_arbiter;
+ Result WaitAddressArbiter(uintptr_t address, Svc::ArbitrationType arb_type, s32 value,
+ s64 timeout) {
+ R_RETURN(m_address_arbiter.WaitForAddress(address, arb_type, value, timeout));
+ }
- /// The per-process mutex lock instance used for handling various
- /// forms of services, such as lock arbitration, and condition
- /// variable related facilities.
- KConditionVariable m_condition_var;
+ Result GetThreadList(s32* out_num_threads, KProcessAddress out_thread_ids, s32 max_out_count);
- /// Address indicating the location of the process' dedicated TLS region.
- KProcessAddress m_plr_address = 0;
+ static void Switch(KProcess* cur_process, KProcess* next_process);
- /// Address indicating the location of the process's entry point.
- KProcessAddress m_code_address = 0;
+public:
+ // Attempts to insert a watchpoint into a free slot. Returns false if none are available.
+ bool InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type);
- /// Random values for svcGetInfo RandomEntropy
- std::array<u64, RANDOM_ENTROPY_SIZE> m_random_entropy{};
+ // Attempts to remove the watchpoint specified by the given parameters.
+ bool RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type);
- /// List of threads that are running with this process as their owner.
- std::list<KThread*> m_thread_list;
+ const std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>& GetWatchpoints() const {
+ return m_watchpoints;
+ }
- /// List of shared memory that are running with this process as their owner.
- std::list<KSharedMemoryInfo*> m_shared_memory_list;
+public:
+ Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
+ bool is_hbl);
- /// Address of the top of the main thread's stack
- KProcessAddress m_main_thread_stack_top{};
+ void LoadModule(CodeSet code_set, KProcessAddress base_addr);
- /// Size of the main thread's stack
- std::size_t m_main_thread_stack_size{};
+ Core::Memory::Memory& GetMemory() const;
- /// Memory usage capacity for the process
- std::size_t m_memory_usage_capacity{};
+public:
+ // Overridden parent functions.
+ bool IsInitialized() const override {
+ return m_is_initialized;
+ }
- /// Process total image size
- std::size_t m_image_size{};
+ static void PostDestroy(uintptr_t arg) {}
- /// Schedule count of this process
- s64 m_schedule_count{};
+ void Finalize() override;
- size_t m_memory_release_hint{};
+ u64 GetIdImpl() const {
+ return this->GetProcessId();
+ }
+ u64 GetId() const override {
+ return this->GetIdImpl();
+ }
- std::string name{};
+ virtual bool IsSignaled() const override {
+ ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
+ return m_is_signaled;
+ }
- bool m_is_signaled{};
- bool m_is_suspended{};
- bool m_is_immortal{};
- bool m_is_handle_table_initialized{};
- bool m_is_initialized{};
- bool m_is_hbl{};
+ void DoWorkerTaskImpl();
- std::atomic<u16> m_num_running_threads{};
+private:
+ void ChangeState(State new_state) {
+ if (m_state != new_state) {
+ m_state = new_state;
+ m_is_signaled = true;
+ this->NotifyAvailable();
+ }
+ }
- std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_running_threads{};
- std::array<u64, Core::Hardware::NUM_CPU_CORES> m_running_thread_idle_counts{};
- std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_pinned_threads{};
- std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> m_watchpoints{};
- std::map<KProcessAddress, u64> m_debug_page_refcounts;
+ Result InitializeHandleTable(s32 size) {
+ // Try to initialize the handle table.
+ R_TRY(m_handle_table.Initialize(size));
- KThread* m_exception_thread{};
+ // We succeeded, so note that we did.
+ m_is_handle_table_initialized = true;
+ R_SUCCEED();
+ }
- KLightLock m_state_lock;
- KLightLock m_list_lock;
+ void FinalizeHandleTable() {
+ // Finalize the table.
+ m_handle_table.Finalize();
- using TLPTree =
- Common::IntrusiveRedBlackTreeBaseTraits<KThreadLocalPage>::TreeType<KThreadLocalPage>;
- using TLPIterator = TLPTree::iterator;
- TLPTree m_fully_used_tlp_tree;
- TLPTree m_partially_used_tlp_tree;
+ // Note that the table is finalized.
+ m_is_handle_table_initialized = false;
+ }
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp
index d8143c650..1bce63a56 100644
--- a/src/core/hle/kernel/k_scheduler.cpp
+++ b/src/core/hle/kernel/k_scheduler.cpp
@@ -190,7 +190,7 @@ u64 KScheduler::UpdateHighestPriorityThread(KThread* highest_thread) {
if (m_state.should_count_idle) {
if (highest_thread != nullptr) [[likely]] {
if (KProcess* process = highest_thread->GetOwnerProcess(); process != nullptr) {
- process->SetRunningThread(m_core_id, highest_thread, m_state.idle_count);
+ process->SetRunningThread(m_core_id, highest_thread, m_state.idle_count, 0);
}
} else {
m_state.idle_count++;
@@ -356,7 +356,7 @@ void KScheduler::SwitchThread(KThread* next_thread) {
const s64 tick_diff = cur_tick - prev_tick;
cur_thread->AddCpuTime(m_core_id, tick_diff);
if (cur_process != nullptr) {
- cur_process->UpdateCPUTimeTicks(tick_diff);
+ cur_process->AddCpuTime(tick_diff);
}
m_last_context_switch_time = cur_tick;
diff --git a/src/core/hle/kernel/k_system_resource.cpp b/src/core/hle/kernel/k_system_resource.cpp
index e6c8d589a..07e92aa80 100644
--- a/src/core/hle/kernel/k_system_resource.cpp
+++ b/src/core/hle/kernel/k_system_resource.cpp
@@ -1,25 +1,100 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "core/core.h"
+#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_system_resource.h"
namespace Kernel {
Result KSecureSystemResource::Initialize(size_t size, KResourceLimit* resource_limit,
KMemoryManager::Pool pool) {
- // Unimplemented
- UNREACHABLE();
+ // Set members.
+ m_resource_limit = resource_limit;
+ m_resource_size = size;
+ m_resource_pool = pool;
+
+ // Determine required size for our secure resource.
+ const size_t secure_size = this->CalculateRequiredSecureMemorySize();
+
+ // Reserve memory for our secure resource.
+ KScopedResourceReservation memory_reservation(
+ m_resource_limit, Svc::LimitableResource::PhysicalMemoryMax, secure_size);
+ R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
+
+ // Allocate secure memory.
+ R_TRY(KSystemControl::AllocateSecureMemory(m_kernel, std::addressof(m_resource_address),
+ m_resource_size, static_cast<u32>(m_resource_pool)));
+ ASSERT(m_resource_address != 0);
+
+ // Ensure we clean up the secure memory, if we fail past this point.
+ ON_RESULT_FAILURE {
+ KSystemControl::FreeSecureMemory(m_kernel, m_resource_address, m_resource_size,
+ static_cast<u32>(m_resource_pool));
+ };
+
+ // Check that our allocation is bigger than the reference counts needed for it.
+ const size_t rc_size =
+ Common::AlignUp(KPageTableSlabHeap::CalculateReferenceCountSize(m_resource_size), PageSize);
+ R_UNLESS(m_resource_size > rc_size, ResultOutOfMemory);
+
+ // Get resource pointer.
+ KPhysicalAddress resource_paddr =
+ KPageTable::GetHeapPhysicalAddress(m_kernel.MemoryLayout(), m_resource_address);
+ auto* resource =
+ m_kernel.System().DeviceMemory().GetPointer<KPageTableManager::RefCount>(resource_paddr);
+
+ // Initialize slab heaps.
+ m_dynamic_page_manager.Initialize(m_resource_address + rc_size, m_resource_size - rc_size,
+ PageSize);
+ m_page_table_heap.Initialize(std::addressof(m_dynamic_page_manager), 0, resource);
+ m_memory_block_heap.Initialize(std::addressof(m_dynamic_page_manager), 0);
+ m_block_info_heap.Initialize(std::addressof(m_dynamic_page_manager), 0);
+
+ // Initialize managers.
+ m_page_table_manager.Initialize(std::addressof(m_dynamic_page_manager),
+ std::addressof(m_page_table_heap));
+ m_memory_block_slab_manager.Initialize(std::addressof(m_dynamic_page_manager),
+ std::addressof(m_memory_block_heap));
+ m_block_info_manager.Initialize(std::addressof(m_dynamic_page_manager),
+ std::addressof(m_block_info_heap));
+
+ // Set our managers.
+ this->SetManagers(m_memory_block_slab_manager, m_block_info_manager, m_page_table_manager);
+
+ // Commit the memory reservation.
+ memory_reservation.Commit();
+
+ // Open reference to our resource limit.
+ m_resource_limit->Open();
+
+ // Set ourselves as initialized.
+ m_is_initialized = true;
+
+ R_SUCCEED();
}
void KSecureSystemResource::Finalize() {
- // Unimplemented
- UNREACHABLE();
+ // Check that we have no outstanding allocations.
+ ASSERT(m_memory_block_slab_manager.GetUsed() == 0);
+ ASSERT(m_block_info_manager.GetUsed() == 0);
+ ASSERT(m_page_table_manager.GetUsed() == 0);
+
+ // Free our secure memory.
+ KSystemControl::FreeSecureMemory(m_kernel, m_resource_address, m_resource_size,
+ static_cast<u32>(m_resource_pool));
+
+ // Release the memory reservation.
+ m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax,
+ this->CalculateRequiredSecureMemorySize());
+
+ // Close reference to our resource limit.
+ m_resource_limit->Close();
}
size_t KSecureSystemResource::CalculateRequiredSecureMemorySize(size_t size,
KMemoryManager::Pool pool) {
- // Unimplemented
- UNREACHABLE();
+ return KSystemControl::CalculateRequiredSecureMemorySize(size, static_cast<u32>(pool));
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp
index 7df8fd7f7..a6deb50ec 100644
--- a/src/core/hle/kernel/k_thread.cpp
+++ b/src/core/hle/kernel/k_thread.cpp
@@ -122,16 +122,15 @@ Result KThread::Initialize(KThreadFunction func, uintptr_t arg, KProcessAddress
case ThreadType::Main:
ASSERT(arg == 0);
[[fallthrough]];
- case ThreadType::HighPriority:
- [[fallthrough]];
- case ThreadType::Dummy:
- [[fallthrough]];
case ThreadType::User:
ASSERT(((owner == nullptr) ||
(owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask()));
ASSERT(((owner == nullptr) || (prio > Svc::LowestThreadPriority) ||
(owner->GetPriorityMask() | (1ULL << prio)) == owner->GetPriorityMask()));
break;
+ case ThreadType::HighPriority:
+ case ThreadType::Dummy:
+ break;
case ThreadType::Kernel:
UNIMPLEMENTED();
break;
@@ -216,6 +215,7 @@ Result KThread::Initialize(KThreadFunction func, uintptr_t arg, KProcessAddress
// Setup the TLS, if needed.
if (type == ThreadType::User) {
R_TRY(owner->CreateThreadLocalRegion(std::addressof(m_tls_address)));
+ owner->GetMemory().ZeroBlock(m_tls_address, Svc::ThreadLocalRegionSize);
}
m_parent = owner;
@@ -403,7 +403,7 @@ void KThread::StartTermination() {
if (m_parent != nullptr) {
m_parent->ReleaseUserException(this);
if (m_parent->GetPinnedThread(GetCurrentCoreId(m_kernel)) == this) {
- m_parent->UnpinCurrentThread(m_core_id);
+ m_parent->UnpinCurrentThread();
}
}
@@ -415,10 +415,6 @@ void KThread::StartTermination() {
m_parent->ClearRunningThread(this);
}
- // Signal.
- m_signaled = true;
- KSynchronizationObject::NotifyAvailable();
-
// Clear previous thread in KScheduler.
KScheduler::ClearPreviousThread(m_kernel, this);
@@ -437,6 +433,13 @@ void KThread::FinishTermination() {
}
}
+ // Acquire the scheduler lock.
+ KScopedSchedulerLock sl{m_kernel};
+
+ // Signal.
+ m_signaled = true;
+ KSynchronizationObject::NotifyAvailable();
+
// Close the thread.
this->Close();
}
@@ -820,7 +823,7 @@ void KThread::CloneFpuStatus() {
ASSERT(this->GetOwnerProcess() != nullptr);
ASSERT(this->GetOwnerProcess() == GetCurrentProcessPointer(m_kernel));
- if (this->GetOwnerProcess()->Is64BitProcess()) {
+ if (this->GetOwnerProcess()->Is64Bit()) {
// Clone FPSR and FPCR.
ThreadContext64 cur_ctx{};
m_kernel.System().CurrentArmInterface().SaveContext(cur_ctx);
@@ -923,7 +926,7 @@ Result KThread::GetThreadContext3(Common::ScratchBuffer<u8>& out) {
// If we're not terminating, get the thread's user context.
if (!this->IsTerminationRequested()) {
- if (m_parent->Is64BitProcess()) {
+ if (m_parent->Is64Bit()) {
// Mask away mode bits, interrupt bits, IL bit, and other reserved bits.
auto context = GetContext64();
context.pstate &= 0xFF0FFE20;
@@ -1174,6 +1177,9 @@ Result KThread::Run() {
owner->IncrementRunningThreadCount();
}
+ // Open a reference, now that we're running.
+ this->Open();
+
// Set our state and finish.
this->SetState(ThreadState::Runnable);
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index d178c2453..e1f80b04f 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -721,6 +721,7 @@ private:
// For core KThread implementation
ThreadContext32 m_thread_context_32{};
ThreadContext64 m_thread_context_64{};
+ Common::IntrusiveListNode m_process_list_node;
Common::IntrusiveRedBlackTreeNode m_condvar_arbiter_tree_node{};
s32 m_priority{};
using ConditionVariableThreadTreeTraits =
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 24433d32b..4a1559291 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -101,35 +101,31 @@ struct KernelCore::Impl {
void InitializeCores() {
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
- cores[core_id]->Initialize((*application_process).Is64BitProcess());
+ cores[core_id]->Initialize((*application_process).Is64Bit());
system.ApplicationMemory().SetCurrentPageTable(*application_process, core_id);
}
}
- void CloseApplicationProcess() {
- KProcess* old_process = application_process.exchange(nullptr);
- if (old_process == nullptr) {
- return;
- }
-
- // old_process->Close();
- // TODO: The process should be destroyed based on accurate ref counting after
- // calling Close(). Adding a manual Destroy() call instead to avoid a memory leak.
- old_process->Finalize();
- old_process->Destroy();
+ void TerminateApplicationProcess() {
+ application_process.load()->Terminate();
}
void Shutdown() {
is_shutting_down.store(true, std::memory_order_relaxed);
SCOPE_EXIT({ is_shutting_down.store(false, std::memory_order_relaxed); });
- process_list.clear();
-
CloseServices();
+ auto* old_process = application_process.exchange(nullptr);
+ if (old_process) {
+ old_process->Close();
+ }
+
+ process_list.clear();
+
next_object_id = 0;
- next_kernel_process_id = KProcess::InitialKIPIDMin;
- next_user_process_id = KProcess::ProcessIDMin;
+ next_kernel_process_id = KProcess::InitialProcessIdMin;
+ next_user_process_id = KProcess::ProcessIdMin;
next_thread_id = 1;
global_handle_table->Finalize();
@@ -176,8 +172,6 @@ struct KernelCore::Impl {
}
}
- CloseApplicationProcess();
-
// Track kernel objects that were not freed on shutdown
{
std::scoped_lock lk{registered_objects_lock};
@@ -344,6 +338,8 @@ struct KernelCore::Impl {
// Create the system page table managers.
app_system_resource = std::make_unique<KSystemResource>(kernel);
sys_system_resource = std::make_unique<KSystemResource>(kernel);
+ KAutoObject::Create(std::addressof(*app_system_resource));
+ KAutoObject::Create(std::addressof(*sys_system_resource));
// Set the managers for the system resources.
app_system_resource->SetManagers(*app_memory_block_manager, *app_block_info_manager,
@@ -792,8 +788,8 @@ struct KernelCore::Impl {
std::mutex registered_in_use_objects_lock;
std::atomic<u32> next_object_id{0};
- std::atomic<u64> next_kernel_process_id{KProcess::InitialKIPIDMin};
- std::atomic<u64> next_user_process_id{KProcess::ProcessIDMin};
+ std::atomic<u64> next_kernel_process_id{KProcess::InitialProcessIdMin};
+ std::atomic<u64> next_user_process_id{KProcess::ProcessIdMin};
std::atomic<u64> next_thread_id{1};
// Lists all processes that exist in the current session.
@@ -924,10 +920,6 @@ const KProcess* KernelCore::ApplicationProcess() const {
return impl->application_process;
}
-void KernelCore::CloseApplicationProcess() {
- impl->CloseApplicationProcess();
-}
-
const std::vector<KProcess*>& KernelCore::GetProcessList() const {
return impl->process_list;
}
@@ -1128,8 +1120,8 @@ std::jthread KernelCore::RunOnHostCoreProcess(std::string&& process_name,
std::function<void()> func) {
// Make a new process.
KProcess* process = KProcess::Create(*this);
- ASSERT(R_SUCCEEDED(KProcess::Initialize(process, System(), "", KProcess::ProcessType::Userland,
- GetSystemResourceLimit())));
+ ASSERT(R_SUCCEEDED(
+ process->Initialize(Svc::CreateProcessParameter{}, GetSystemResourceLimit(), false)));
// Ensure that we don't hold onto any extra references.
SCOPE_EXIT({ process->Close(); });
@@ -1156,8 +1148,8 @@ void KernelCore::RunOnGuestCoreProcess(std::string&& process_name, std::function
// Make a new process.
KProcess* process = KProcess::Create(*this);
- ASSERT(R_SUCCEEDED(KProcess::Initialize(process, System(), "", KProcess::ProcessType::Userland,
- GetSystemResourceLimit())));
+ ASSERT(R_SUCCEEDED(
+ process->Initialize(Svc::CreateProcessParameter{}, GetSystemResourceLimit(), false)));
// Ensure that we don't hold onto any extra references.
SCOPE_EXIT({ process->Close(); });
@@ -1266,7 +1258,8 @@ const Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() const {
void KernelCore::SuspendApplication(bool suspended) {
const bool should_suspend{exception_exited || suspended};
- const auto activity = should_suspend ? ProcessActivity::Paused : ProcessActivity::Runnable;
+ const auto activity =
+ should_suspend ? Svc::ProcessActivity::Paused : Svc::ProcessActivity::Runnable;
// Get the application process.
KScopedAutoObject<KProcess> process = ApplicationProcess();
@@ -1300,6 +1293,8 @@ void KernelCore::SuspendApplication(bool suspended) {
}
void KernelCore::ShutdownCores() {
+ impl->TerminateApplicationProcess();
+
KScopedSchedulerLock lk{*this};
for (auto* thread : impl->shutdown_threads) {
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index d5b08eeb5..d8086c0ea 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -134,9 +134,6 @@ public:
/// Retrieves a const pointer to the application process.
const KProcess* ApplicationProcess() const;
- /// Closes the application process.
- void CloseApplicationProcess();
-
/// Retrieves the list of processes.
const std::vector<KProcess*>& GetProcessList() const;
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 871d541d4..b76683969 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -4426,7 +4426,7 @@ void Call(Core::System& system, u32 imm) {
auto& kernel = system.Kernel();
kernel.EnterSVCProfile();
- if (GetCurrentProcess(system.Kernel()).Is64BitProcess()) {
+ if (GetCurrentProcess(system.Kernel()).Is64Bit()) {
Call64(system, imm);
} else {
Call32(system, imm);
diff --git a/src/core/hle/kernel/svc/svc_info.cpp b/src/core/hle/kernel/svc/svc_info.cpp
index f99964028..ada998772 100644
--- a/src/core/hle/kernel/svc/svc_info.cpp
+++ b/src/core/hle/kernel/svc/svc_info.cpp
@@ -86,20 +86,19 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
R_SUCCEED();
case InfoType::TotalMemorySize:
- *result = process->GetTotalPhysicalMemoryAvailable();
+ *result = process->GetTotalUserPhysicalMemorySize();
R_SUCCEED();
case InfoType::UsedMemorySize:
- *result = process->GetTotalPhysicalMemoryUsed();
+ *result = process->GetUsedUserPhysicalMemorySize();
R_SUCCEED();
case InfoType::SystemResourceSizeTotal:
- *result = process->GetSystemResourceSize();
+ *result = process->GetTotalSystemResourceSize();
R_SUCCEED();
case InfoType::SystemResourceSizeUsed:
- LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query system resource usage");
- *result = process->GetSystemResourceUsage();
+ *result = process->GetUsedSystemResourceSize();
R_SUCCEED();
case InfoType::ProgramId:
@@ -111,20 +110,29 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
R_SUCCEED();
case InfoType::TotalNonSystemMemorySize:
- *result = process->GetTotalPhysicalMemoryAvailableWithoutSystemResource();
+ *result = process->GetTotalNonSystemUserPhysicalMemorySize();
R_SUCCEED();
case InfoType::UsedNonSystemMemorySize:
- *result = process->GetTotalPhysicalMemoryUsedWithoutSystemResource();
+ *result = process->GetUsedNonSystemUserPhysicalMemorySize();
R_SUCCEED();
case InfoType::IsApplication:
LOG_WARNING(Kernel_SVC, "(STUBBED) Assuming process is application");
- *result = true;
+ *result = process->IsApplication();
R_SUCCEED();
case InfoType::FreeThreadCount:
- *result = process->GetFreeThreadCount();
+ if (KResourceLimit* resource_limit = process->GetResourceLimit();
+ resource_limit != nullptr) {
+ const auto current_value =
+ resource_limit->GetCurrentValue(Svc::LimitableResource::ThreadCountMax);
+ const auto limit_value =
+ resource_limit->GetLimitValue(Svc::LimitableResource::ThreadCountMax);
+ *result = limit_value - current_value;
+ } else {
+ *result = 0;
+ }
R_SUCCEED();
default:
@@ -161,7 +169,7 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
case InfoType::RandomEntropy:
R_UNLESS(handle == 0, ResultInvalidHandle);
- R_UNLESS(info_sub_id < KProcess::RANDOM_ENTROPY_SIZE, ResultInvalidCombination);
+ R_UNLESS(info_sub_id < 4, ResultInvalidCombination);
*result = GetCurrentProcess(system.Kernel()).GetRandomEntropy(info_sub_id);
R_SUCCEED();
diff --git a/src/core/hle/kernel/svc/svc_lock.cpp b/src/core/hle/kernel/svc/svc_lock.cpp
index 1d7bc4246..5f0833fcb 100644
--- a/src/core/hle/kernel/svc/svc_lock.cpp
+++ b/src/core/hle/kernel/svc/svc_lock.cpp
@@ -17,7 +17,7 @@ Result ArbitrateLock(Core::System& system, Handle thread_handle, u64 address, u3
R_UNLESS(!IsKernelAddress(address), ResultInvalidCurrentMemory);
R_UNLESS(Common::IsAligned(address, sizeof(u32)), ResultInvalidAddress);
- R_RETURN(GetCurrentProcess(system.Kernel()).WaitForAddress(thread_handle, address, tag));
+ R_RETURN(KConditionVariable::WaitForAddress(system.Kernel(), thread_handle, address, tag));
}
/// Unlock a mutex
@@ -28,7 +28,7 @@ Result ArbitrateUnlock(Core::System& system, u64 address) {
R_UNLESS(!IsKernelAddress(address), ResultInvalidCurrentMemory);
R_UNLESS(Common::IsAligned(address, sizeof(u32)), ResultInvalidAddress);
- R_RETURN(GetCurrentProcess(system.Kernel()).SignalToAddress(address));
+ R_RETURN(KConditionVariable::SignalToAddress(system.Kernel(), address));
}
Result ArbitrateLock64(Core::System& system, Handle thread_handle, uint64_t address, uint32_t tag) {
diff --git a/src/core/hle/kernel/svc/svc_physical_memory.cpp b/src/core/hle/kernel/svc/svc_physical_memory.cpp
index d3545f232..99330d02a 100644
--- a/src/core/hle/kernel/svc/svc_physical_memory.cpp
+++ b/src/core/hle/kernel/svc/svc_physical_memory.cpp
@@ -46,7 +46,7 @@ Result MapPhysicalMemory(Core::System& system, u64 addr, u64 size) {
KProcess* const current_process{GetCurrentProcessPointer(system.Kernel())};
auto& page_table{current_process->GetPageTable()};
- if (current_process->GetSystemResourceSize() == 0) {
+ if (current_process->GetTotalSystemResourceSize() == 0) {
LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
R_THROW(ResultInvalidState);
}
@@ -95,7 +95,7 @@ Result UnmapPhysicalMemory(Core::System& system, u64 addr, u64 size) {
KProcess* const current_process{GetCurrentProcessPointer(system.Kernel())};
auto& page_table{current_process->GetPageTable()};
- if (current_process->GetSystemResourceSize() == 0) {
+ if (current_process->GetTotalSystemResourceSize() == 0) {
LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
R_THROW(ResultInvalidState);
}
diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp
index 8ebc1bd1c..6c79cfd8d 100644
--- a/src/core/hle/kernel/svc/svc_synchronization.cpp
+++ b/src/core/hle/kernel/svc/svc_synchronization.cpp
@@ -132,7 +132,7 @@ void SynchronizePreemptionState(Core::System& system) {
GetCurrentThread(kernel).ClearInterruptFlag();
// Unpin the current thread.
- cur_process->UnpinCurrentThread(core_id);
+ cur_process->UnpinCurrentThread();
}
}
diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp
index 933b82e30..755fd62b5 100644
--- a/src/core/hle/kernel/svc/svc_thread.cpp
+++ b/src/core/hle/kernel/svc/svc_thread.cpp
@@ -85,10 +85,6 @@ Result StartThread(Core::System& system, Handle thread_handle) {
// Try to start the thread.
R_TRY(thread->Run());
- // If we succeeded, persist a reference to the thread.
- thread->Open();
- system.Kernel().RegisterInUseObject(thread.GetPointerUnsafe());
-
R_SUCCEED();
}
@@ -99,7 +95,6 @@ void ExitThread(Core::System& system) {
auto* const current_thread = GetCurrentThreadPointer(system.Kernel());
system.GlobalSchedulerContext().RemoveThread(current_thread);
current_thread->Exit();
- system.Kernel().UnregisterInUseObject(current_thread);
}
/// Sleep the current thread
@@ -260,7 +255,7 @@ Result GetThreadList(Core::System& system, s32* out_num_threads, u64 out_thread_
auto list_iter = thread_list.cbegin();
for (std::size_t i = 0; i < copy_amount; ++i, ++list_iter) {
- memory.Write64(out_thread_ids, (*list_iter)->GetThreadId());
+ memory.Write64(out_thread_ids, list_iter->GetThreadId());
out_thread_ids += sizeof(u64);
}
diff --git a/src/core/hle/kernel/svc_generator.py b/src/core/hle/kernel/svc_generator.py
index 7fcbb1ba1..5531faac6 100644
--- a/src/core/hle/kernel/svc_generator.py
+++ b/src/core/hle/kernel/svc_generator.py
@@ -592,7 +592,7 @@ void Call(Core::System& system, u32 imm) {
auto& kernel = system.Kernel();
kernel.EnterSVCProfile();
- if (GetCurrentProcess(system.Kernel()).Is64BitProcess()) {
+ if (GetCurrentProcess(system.Kernel()).Is64Bit()) {
Call64(system, imm);
} else {
Call32(system, imm);
diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h
index 251e6013c..50de02e36 100644
--- a/src/core/hle/kernel/svc_types.h
+++ b/src/core/hle/kernel/svc_types.h
@@ -604,13 +604,57 @@ enum class ProcessActivity : u32 {
Paused,
};
+enum class CreateProcessFlag : u32 {
+ // Is 64 bit?
+ Is64Bit = (1 << 0),
+
+ // What kind of address space?
+ AddressSpaceShift = 1,
+ AddressSpaceMask = (7 << AddressSpaceShift),
+ AddressSpace32Bit = (0 << AddressSpaceShift),
+ AddressSpace64BitDeprecated = (1 << AddressSpaceShift),
+ AddressSpace32BitWithoutAlias = (2 << AddressSpaceShift),
+ AddressSpace64Bit = (3 << AddressSpaceShift),
+
+ // Should JIT debug be done on crash?
+ EnableDebug = (1 << 4),
+
+ // Should ASLR be enabled for the process?
+ EnableAslr = (1 << 5),
+
+ // Is the process an application?
+ IsApplication = (1 << 6),
+
+ // 4.x deprecated: Should use secure memory?
+ DeprecatedUseSecureMemory = (1 << 7),
+
+ // 5.x+ Pool partition type.
+ PoolPartitionShift = 7,
+ PoolPartitionMask = (0xF << PoolPartitionShift),
+ PoolPartitionApplication = (0 << PoolPartitionShift),
+ PoolPartitionApplet = (1 << PoolPartitionShift),
+ PoolPartitionSystem = (2 << PoolPartitionShift),
+ PoolPartitionSystemNonSecure = (3 << PoolPartitionShift),
+
+ // 7.x+ Should memory allocation be optimized? This requires IsApplication.
+ OptimizeMemoryAllocation = (1 << 11),
+
+ // 11.x+ DisableDeviceAddressSpaceMerge.
+ DisableDeviceAddressSpaceMerge = (1 << 12),
+
+ // Mask of all flags.
+ All = Is64Bit | AddressSpaceMask | EnableDebug | EnableAslr | IsApplication |
+ PoolPartitionMask | OptimizeMemoryAllocation | DisableDeviceAddressSpaceMerge,
+};
+DECLARE_ENUM_FLAG_OPERATORS(CreateProcessFlag);
+
struct CreateProcessParameter {
std::array<char, 12> name;
u32 version;
u64 program_id;
u64 code_address;
s32 code_num_pages;
- u32 flags;
+ CreateProcessFlag flags;
Handle reslimit;
s32 system_resource_num_pages;
};
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 0886531b2..242ea6665 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -805,7 +805,9 @@ ILockAccessor::ILockAccessor(Core::System& system_)
lock_event = service_context.CreateEvent("ILockAccessor::LockEvent");
}
-ILockAccessor::~ILockAccessor() = default;
+ILockAccessor::~ILockAccessor() {
+ service_context.CloseEvent(lock_event);
+};
void ILockAccessor::TryLock(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
@@ -918,7 +920,9 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
}
-ICommonStateGetter::~ICommonStateGetter() = default;
+ICommonStateGetter::~ICommonStateGetter() {
+ service_context.CloseEvent(sleep_lock_event);
+};
void ICommonStateGetter::GetBootMode(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
diff --git a/src/core/hle/service/am/applets/applet_cabinet.cpp b/src/core/hle/service/am/applets/applet_cabinet.cpp
index 19ed184e8..b379dadeb 100644
--- a/src/core/hle/service/am/applets/applet_cabinet.cpp
+++ b/src/core/hle/service/am/applets/applet_cabinet.cpp
@@ -25,7 +25,9 @@ Cabinet::Cabinet(Core::System& system_, LibraryAppletMode applet_mode_,
service_context.CreateEvent("CabinetApplet:AvailabilityChangeEvent");
}
-Cabinet::~Cabinet() = default;
+Cabinet::~Cabinet() {
+ service_context.CloseEvent(availability_change_event);
+};
void Cabinet::Initialize() {
Applet::Initialize();
diff --git a/src/core/hle/service/hid/controllers/palma.cpp b/src/core/hle/service/hid/controllers/palma.cpp
index 14c67e454..73a2a2b91 100644
--- a/src/core/hle/service/hid/controllers/palma.cpp
+++ b/src/core/hle/service/hid/controllers/palma.cpp
@@ -19,7 +19,9 @@ Controller_Palma::Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared
operation_complete_event = service_context.CreateEvent("hid:PalmaOperationCompleteEvent");
}
-Controller_Palma::~Controller_Palma() = default;
+Controller_Palma::~Controller_Palma() {
+ service_context.CloseEvent(operation_complete_event);
+};
void Controller_Palma::OnInit() {}
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 4d70006c1..929dd5f03 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -2757,6 +2757,10 @@ public:
joy_detach_event = service_context.CreateEvent("HidSys::JoyDetachEvent");
}
+ ~HidSys() {
+ service_context.CloseEvent(joy_detach_event);
+ };
+
private:
void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) {
LOG_WARNING(Service_HID, "called");
diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.cpp b/src/core/hle/service/hid/hidbus/hidbus_base.cpp
index ee522c36e..8c44f93e8 100644
--- a/src/core/hle/service/hid/hidbus/hidbus_base.cpp
+++ b/src/core/hle/service/hid/hidbus/hidbus_base.cpp
@@ -13,7 +13,10 @@ HidbusBase::HidbusBase(Core::System& system_, KernelHelpers::ServiceContext& ser
: system(system_), service_context(service_context_) {
send_command_async_event = service_context.CreateEvent("hidbus:SendCommandAsyncEvent");
}
-HidbusBase::~HidbusBase() = default;
+
+HidbusBase::~HidbusBase() {
+ service_context.CloseEvent(send_command_async_event);
+};
void HidbusBase::ActivateDevice() {
if (is_activated) {
diff --git a/src/core/hle/service/kernel_helpers.cpp b/src/core/hle/service/kernel_helpers.cpp
index 6a313a03b..f51e63564 100644
--- a/src/core/hle/service/kernel_helpers.cpp
+++ b/src/core/hle/service/kernel_helpers.cpp
@@ -21,10 +21,8 @@ ServiceContext::ServiceContext(Core::System& system_, std::string name_)
// Create the process.
process = Kernel::KProcess::Create(kernel);
- ASSERT(Kernel::KProcess::Initialize(process, system_, std::move(name_),
- Kernel::KProcess::ProcessType::KernelInternal,
- kernel.GetSystemResourceLimit())
- .IsSuccess());
+ ASSERT(R_SUCCEEDED(process->Initialize(Kernel::Svc::CreateProcessParameter{},
+ kernel.GetSystemResourceLimit(), false)));
// Register the process.
Kernel::KProcess::Register(kernel, process);
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_core.cpp b/src/core/hle/service/nvnflinger/buffer_queue_core.cpp
index 2dbe29616..ed66f6f5b 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_core.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_queue_core.cpp
@@ -41,7 +41,7 @@ bool BufferQueueCore::WaitForDequeueCondition(std::unique_lock<std::mutex>& lk)
s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const {
// If DequeueBuffer is allowed to error out, we don't have to add an extra buffer.
if (!use_async_buffer) {
- return max_acquired_buffer_count;
+ return 0;
}
if (dequeue_buffer_cannot_block || async) {
@@ -52,7 +52,7 @@ s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const {
}
s32 BufferQueueCore::GetMinMaxBufferCountLocked(bool async) const {
- return GetMinUndequeuedBufferCountLocked(async) + 1;
+ return GetMinUndequeuedBufferCountLocked(async);
}
s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const {
@@ -61,7 +61,7 @@ s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const {
if (override_max_buffer_count != 0) {
ASSERT(override_max_buffer_count >= min_buffer_count);
- max_buffer_count = override_max_buffer_count;
+ return override_max_buffer_count;
}
// Any buffers that are dequeued by the producer or sitting in the queue waiting to be consumed
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
index dc6917d5d..6e7a49658 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
@@ -134,7 +134,7 @@ Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found, St
const s32 max_buffer_count = core->GetMaxBufferCountLocked(async);
if (async && core->override_max_buffer_count) {
if (core->override_max_buffer_count < max_buffer_count) {
- LOG_ERROR(Service_Nvnflinger, "async mode is invalid with buffer count override");
+ *found = BufferQueueCore::INVALID_BUFFER_SLOT;
return Status::BadValue;
}
}
@@ -142,7 +142,8 @@ Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found, St
// Free up any buffers that are in slots beyond the max buffer count
for (s32 s = max_buffer_count; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
ASSERT(slots[s].buffer_state == BufferState::Free);
- if (slots[s].graphic_buffer != nullptr) {
+ if (slots[s].graphic_buffer != nullptr && slots[s].buffer_state == BufferState::Free &&
+ !slots[s].is_preallocated) {
core->FreeBufferLocked(s);
*return_flags |= Status::ReleaseAllBuffers;
}
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index a07c621d9..bebb45eae 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -66,7 +66,6 @@ Nvnflinger::Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_
"ScreenComposition",
[this](std::uintptr_t, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
- { const auto lock_guard = Lock(); }
vsync_signal.Set();
return std::chrono::nanoseconds(GetNextTicks());
});
@@ -99,6 +98,7 @@ Nvnflinger::~Nvnflinger() {
}
ShutdownLayers();
+ vsync_thread = {};
if (nvdrv) {
nvdrv->Close(disp_fd);
@@ -106,6 +106,7 @@ Nvnflinger::~Nvnflinger() {
}
void Nvnflinger::ShutdownLayers() {
+ const auto lock_guard = Lock();
for (auto& display : displays) {
for (size_t layer = 0; layer < display.GetNumLayers(); ++layer) {
display.GetLayer(layer).Core().NotifyShutdown();
@@ -229,16 +230,6 @@ VI::Layer* Nvnflinger::FindLayer(u64 display_id, u64 layer_id) {
return display->FindLayer(layer_id);
}
-const VI::Layer* Nvnflinger::FindLayer(u64 display_id, u64 layer_id) const {
- const auto* const display = FindDisplay(display_id);
-
- if (display == nullptr) {
- return nullptr;
- }
-
- return display->FindLayer(layer_id);
-}
-
VI::Layer* Nvnflinger::FindOrCreateLayer(u64 display_id, u64 layer_id) {
auto* const display = FindDisplay(display_id);
@@ -288,7 +279,6 @@ void Nvnflinger::Compose() {
auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd);
ASSERT(nvdisp);
- guard->unlock();
Common::Rectangle<int> crop_rect{
static_cast<int>(buffer.crop.Left()), static_cast<int>(buffer.crop.Top()),
static_cast<int>(buffer.crop.Right()), static_cast<int>(buffer.crop.Bottom())};
@@ -299,7 +289,6 @@ void Nvnflinger::Compose() {
buffer.fence.fences, buffer.fence.num_fences);
MicroProfileFlip();
- guard->lock();
swap_interval = buffer.swap_interval;
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h
index 14c783582..959d8b46b 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.h
+++ b/src/core/hle/service/nvnflinger/nvnflinger.h
@@ -117,9 +117,6 @@ private:
/// Finds the layer identified by the specified ID in the desired display.
[[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id);
- /// Finds the layer identified by the specified ID in the desired display.
- [[nodiscard]] const VI::Layer* FindLayer(u64 display_id, u64 layer_id) const;
-
/// Finds the layer identified by the specified ID in the desired display,
/// or creates the layer if it is not found.
/// To be used when the system expects the specified ID to already exist.
diff --git a/src/core/hle/service/pctl/pctl_module.cpp b/src/core/hle/service/pctl/pctl_module.cpp
index 938330dd0..6a7fd72bc 100644
--- a/src/core/hle/service/pctl/pctl_module.cpp
+++ b/src/core/hle/service/pctl/pctl_module.cpp
@@ -141,6 +141,12 @@ public:
service_context.CreateEvent("IParentalControlService::RequestSuspensionEvent");
}
+ ~IParentalControlService() {
+ service_context.CloseEvent(synchronization_event);
+ service_context.CloseEvent(unlinked_event);
+ service_context.CloseEvent(request_suspension_event);
+ };
+
private:
bool CheckFreeCommunicationPermissionImpl() const {
if (states.temporary_unlocked) {
diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp
index f9cf2dda3..d92499f05 100644
--- a/src/core/hle/service/pm/pm.cpp
+++ b/src/core/hle/service/pm/pm.cpp
@@ -37,7 +37,7 @@ std::optional<Kernel::KProcess*> SearchProcessList(
void GetApplicationPidGeneric(HLERequestContext& ctx,
const std::vector<Kernel::KProcess*>& process_list) {
const auto process = SearchProcessList(process_list, [](const auto& proc) {
- return proc->GetProcessId() == Kernel::KProcess::ProcessIDMin;
+ return proc->GetProcessId() == Kernel::KProcess::ProcessIdMin;
});
IPC::ResponseBuilder rb{ctx, 4};
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index ed875d444..5d168cbc1 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -116,7 +116,7 @@ json GetProcessorStateDataAuto(Core::System& system) {
Core::ARM_Interface::ThreadContext64 context{};
arm.SaveContext(context);
- return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32",
+ return GetProcessorStateData(process->Is64Bit() ? "AArch64" : "AArch32",
GetInteger(process->GetEntryPoint()), context.sp, context.pc,
context.pstate, context.cpu_registers);
}
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index cf51f3481..c9f903213 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -139,7 +139,7 @@ void JoyconDriver::InputThread(std::stop_token stop_token) {
input_thread_running = true;
// Max update rate is 5ms, ensure we are always able to read a bit faster
- constexpr int ThreadDelay = 2;
+ constexpr int ThreadDelay = 3;
std::vector<u8> buffer(MaxBufferSize);
while (!stop_token.stop_requested()) {
@@ -163,6 +163,17 @@ void JoyconDriver::InputThread(std::stop_token stop_token) {
OnNewData(buffer);
}
+ if (!vibration_queue.Empty()) {
+ VibrationValue vibration_value;
+ vibration_queue.Pop(vibration_value);
+ last_vibration_result = rumble_protocol->SendVibration(vibration_value);
+ }
+
+ // We can't keep up with vibrations. Start skipping.
+ while (vibration_queue.Size() > 6) {
+ vibration_queue.Pop();
+ }
+
std::this_thread::yield();
}
@@ -402,7 +413,8 @@ Common::Input::DriverResult JoyconDriver::SetVibration(const VibrationValue& vib
if (disable_input_thread) {
return Common::Input::DriverResult::HandleInUse;
}
- return rumble_protocol->SendVibration(vibration);
+ vibration_queue.Push(vibration);
+ return last_vibration_result;
}
Common::Input::DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index 335e12cc3..5355780fb 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -9,6 +9,7 @@
#include <span>
#include <thread>
+#include "common/threadsafe_queue.h"
#include "input_common/helpers/joycon_protocol/joycon_types.h"
namespace Common::Input {
@@ -152,6 +153,10 @@ private:
SerialNumber handle_serial_number{}; // Serial number type reported by hidapi
SupportedFeatures supported_features{};
+ /// Queue of vibration request to controllers
+ Common::Input::DriverResult last_vibration_result{Common::Input::DriverResult::Success};
+ Common::SPSCQueue<VibrationValue> vibration_queue;
+
// Thread related
mutable std::mutex mutex;
std::jthread input_thread;
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 9b2698fad..081a574e8 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -1067,8 +1067,7 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
template <class P>
void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
- do {
- channel_state->has_deleted_buffers = false;
+ BufferOperations([&]() {
if (is_indexed) {
UpdateIndexBuffer();
}
@@ -1082,14 +1081,16 @@ void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
if (current_draw_indirect) {
UpdateDrawIndirect();
}
- } while (channel_state->has_deleted_buffers);
+ });
}
template <class P>
void BufferCache<P>::DoUpdateComputeBuffers() {
- UpdateComputeUniformBuffers();
- UpdateComputeStorageBuffers();
- UpdateComputeTextureBuffers();
+ BufferOperations([&]() {
+ UpdateComputeUniformBuffers();
+ UpdateComputeStorageBuffers();
+ UpdateComputeTextureBuffers();
+ });
}
template <class P>
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 804b95989..22bf8cc77 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -358,7 +358,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
.has_broken_spirv_subgroup_mask_vector_extract_dynamic =
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
.has_broken_robust =
- device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Maxwell,
+ device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal,
};
host_info = Shader::HostTranslateInfo{
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 465eac37e..059b7cb40 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -13,6 +13,7 @@
#include "common/microprofile.h"
#include "common/scope_exit.h"
#include "common/settings.h"
+#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/control/channel_state.h"
#include "video_core/engines/draw_manager.h"
#include "video_core/engines/kepler_compute.h"
@@ -285,6 +286,7 @@ void RasterizerVulkan::DrawTexture() {
query_cache.NotifySegment(true);
+ std::scoped_lock l{texture_cache.mutex};
texture_cache.SynchronizeGraphicsDescriptors();
texture_cache.UpdateRenderTargets(false);
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
index d56558a83..daaea2979 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
@@ -190,7 +190,7 @@ void SetupDirtySpecialOps(Tables& tables) {
void SetupDirtyViewportSwizzles(Tables& tables) {
static constexpr size_t swizzle_offset = 6;
for (size_t index = 0; index < Regs::NumViewports; ++index) {
- tables[0][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] =
+ tables[1][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] =
ViewportSwizzles;
}
}
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index 81ef98f61..821f44f1a 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -147,6 +147,9 @@ bool Swapchain::AcquireNextImage() {
case VK_ERROR_OUT_OF_DATE_KHR:
is_outdated = true;
break;
+ case VK_ERROR_SURFACE_LOST_KHR:
+ vk::Check(result);
+ break;
default:
LOG_ERROR(Render_Vulkan, "vkAcquireNextImageKHR returned {}", vk::ToString(result));
break;
@@ -180,6 +183,9 @@ void Swapchain::Present(VkSemaphore render_semaphore) {
case VK_ERROR_OUT_OF_DATE_KHR:
is_outdated = true;
break;
+ case VK_ERROR_SURFACE_LOST_KHR:
+ vk::Check(result);
+ break;
default:
LOG_CRITICAL(Render_Vulkan, "Failed to present with error {}", vk::ToString(result));
break;
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 34208ed74..33e1fb663 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -227,14 +227,14 @@ add_executable(yuzu
yuzu.rc
)
-if (WIN32 AND YUZU_CRASH_DUMPS)
+if (YUZU_CRASH_DUMPS)
target_sources(yuzu PRIVATE
- mini_dump.cpp
- mini_dump.h
+ breakpad.cpp
+ breakpad.h
)
- target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY})
- target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP)
+ target_link_libraries(yuzu PRIVATE libbreakpad_client)
+ target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS)
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index ca0e14fad..515cb7ce6 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -155,18 +155,27 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
UpdateBorderColor(i);
connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
- if (checked) {
- // Hide eventual error message about number of controllers
- ui->labelError->setVisible(false);
- for (std::size_t index = 0; index <= i; ++index) {
- connected_controller_checkboxes[index]->setChecked(checked);
- }
- } else {
- for (std::size_t index = i; index < NUM_PLAYERS; ++index) {
- connected_controller_checkboxes[index]->setChecked(checked);
- }
+ // Reconnect current controller if it was the last one checked
+ // (player number was reduced by more than one)
+ const bool reconnect_first = !checked && i < player_groupboxes.size() - 1 &&
+ player_groupboxes[i + 1]->isChecked();
+
+ // Ensures that connecting a controller changes the number of players
+ if (connected_controller_checkboxes[i]->isChecked() != checked) {
+ // Ensures that the players are always connected in sequential order
+ PropagatePlayerNumberChanged(i, checked, reconnect_first);
}
});
+ connect(connected_controller_checkboxes[i], &QCheckBox::clicked, [this, i](bool checked) {
+ // Reconnect current controller if it was the last one checked
+ // (player number was reduced by more than one)
+ const bool reconnect_first = !checked &&
+ i < connected_controller_checkboxes.size() - 1 &&
+ connected_controller_checkboxes[i + 1]->isChecked();
+
+ // Ensures that the players are always connected in sequential order
+ PropagatePlayerNumberChanged(i, checked, reconnect_first);
+ });
connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
[this, i](int) {
@@ -668,6 +677,29 @@ void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
}
}
+void QtControllerSelectorDialog::PropagatePlayerNumberChanged(size_t player_index, bool checked,
+ bool reconnect_current) {
+ connected_controller_checkboxes[player_index]->setChecked(checked);
+ // Hide eventual error message about number of controllers
+ ui->labelError->setVisible(false);
+
+ if (checked) {
+ // Check all previous buttons when checked
+ if (player_index > 0) {
+ PropagatePlayerNumberChanged(player_index - 1, checked);
+ }
+ } else {
+ // Unchecked all following buttons when unchecked
+ if (player_index < connected_controller_checkboxes.size() - 1) {
+ PropagatePlayerNumberChanged(player_index + 1, checked);
+ }
+ }
+
+ if (reconnect_current) {
+ connected_controller_checkboxes[player_index]->setCheckState(Qt::Checked);
+ }
+}
+
void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h
index 7f0673d06..e5372495d 100644
--- a/src/yuzu/applets/qt_controller.h
+++ b/src/yuzu/applets/qt_controller.h
@@ -100,6 +100,10 @@ private:
// Updates the console mode.
void UpdateDockedState(bool is_handheld);
+ // Enable preceding controllers or disable following ones
+ void PropagatePlayerNumberChanged(size_t player_index, bool checked,
+ bool reconnect_current = false);
+
// Disables and disconnects unsupported players based on the given parameters.
void DisableUnsupportedPlayers();
diff --git a/src/yuzu/breakpad.cpp b/src/yuzu/breakpad.cpp
new file mode 100644
index 000000000..0f6a71ab0
--- /dev/null
+++ b/src/yuzu/breakpad.cpp
@@ -0,0 +1,77 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <ranges>
+
+#if defined(_WIN32)
+#include <client/windows/handler/exception_handler.h>
+#elif defined(__linux__)
+#include <client/linux/handler/exception_handler.h>
+#else
+#error Minidump creation not supported on this platform
+#endif
+
+#include "common/fs/fs_paths.h"
+#include "common/fs/path_util.h"
+#include "yuzu/breakpad.h"
+
+namespace Breakpad {
+
+static void PruneDumpDirectory(const std::filesystem::path& dump_path) {
+ // Code in this function should be exception-safe.
+ struct Entry {
+ std::filesystem::path path;
+ std::filesystem::file_time_type last_write_time;
+ };
+ std::vector<Entry> existing_dumps;
+
+ // Get existing entries.
+ std::error_code ec;
+ std::filesystem::directory_iterator dir(dump_path, ec);
+ for (auto& entry : dir) {
+ if (entry.is_regular_file()) {
+ existing_dumps.push_back(Entry{
+ .path = entry.path(),
+ .last_write_time = entry.last_write_time(ec),
+ });
+ }
+ }
+
+ // Sort descending by creation date.
+ std::ranges::stable_sort(existing_dumps, [](const auto& a, const auto& b) {
+ return a.last_write_time > b.last_write_time;
+ });
+
+ // Delete older dumps.
+ for (size_t i = 5; i < existing_dumps.size(); i++) {
+ std::filesystem::remove(existing_dumps[i].path, ec);
+ }
+}
+
+#if defined(__linux__)
+[[noreturn]] bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context,
+ bool succeeded) {
+ // Prevent time- and space-consuming core dumps from being generated, as we have
+ // already generated a minidump and a core file will not be useful anyway.
+ _exit(1);
+}
+#endif
+
+void InstallCrashHandler() {
+ // Write crash dumps to profile directory.
+ const auto dump_path = GetYuzuPath(Common::FS::YuzuPath::CrashDumpsDir);
+ PruneDumpDirectory(dump_path);
+
+#if defined(_WIN32)
+ // TODO: If we switch to MinGW builds for Windows, this needs to be wrapped in a C API.
+ static google_breakpad::ExceptionHandler eh{dump_path, nullptr, nullptr, nullptr,
+ google_breakpad::ExceptionHandler::HANDLER_ALL};
+#elif defined(__linux__)
+ static google_breakpad::MinidumpDescriptor descriptor{dump_path};
+ static google_breakpad::ExceptionHandler eh{descriptor, nullptr, DumpCallback,
+ nullptr, true, -1};
+#endif
+}
+
+} // namespace Breakpad
diff --git a/src/yuzu/breakpad.h b/src/yuzu/breakpad.h
new file mode 100644
index 000000000..0f911aa9c
--- /dev/null
+++ b/src/yuzu/breakpad.h
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+namespace Breakpad {
+
+void InstallCrashHandler();
+
+}
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index b22fda746..ef421c754 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -27,16 +27,6 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent)
connect(ui->toggle_gdbstub, &QCheckBox::toggled,
[&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); });
-
- connect(ui->create_crash_dumps, &QCheckBox::stateChanged, [&](int) {
- if (crash_dump_warning_shown) {
- return;
- }
- QMessageBox::warning(this, tr("Restart Required"),
- tr("yuzu is required to restart in order to apply this setting."),
- QMessageBox::Ok, QMessageBox::Ok);
- crash_dump_warning_shown = true;
- });
}
ConfigureDebug::~ConfigureDebug() = default;
@@ -89,13 +79,6 @@ void ConfigureDebug::SetConfiguration() {
ui->disable_web_applet->setEnabled(false);
ui->disable_web_applet->setText(tr("Web applet not compiled"));
#endif
-
-#ifdef YUZU_DBGHELP
- ui->create_crash_dumps->setChecked(Settings::values.create_crash_dumps.GetValue());
-#else
- ui->create_crash_dumps->setEnabled(false);
- ui->create_crash_dumps->setText(tr("MiniDump creation not compiled"));
-#endif
}
void ConfigureDebug::ApplyConfiguration() {
@@ -107,7 +90,6 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked();
Settings::values.reporting_services = ui->reporting_services->isChecked();
Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked();
- Settings::values.create_crash_dumps = ui->create_crash_dumps->isChecked();
Settings::values.quest_flag = ui->quest_flag->isChecked();
Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked();
Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 66b8b7459..76fe98924 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -471,13 +471,6 @@
</property>
</widget>
</item>
- <item row="4" column="0">
- <widget class="QCheckBox" name="create_crash_dumps">
- <property name="text">
- <string>Create Minidump After Crash</string>
- </property>
- </widget>
- </item>
<item row="3" column="0">
<widget class="QCheckBox" name="dump_audio_commands">
<property name="toolTip">
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 5a48e388b..3dcad2701 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -101,13 +101,13 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
ui->tabPlayer5, ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8,
};
- player_connected = {
+ connected_controller_checkboxes = {
ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
};
- std::array<QLabel*, 8> player_connected_labels = {
+ std::array<QLabel*, 8> connected_controller_labels = {
ui->label, ui->label_3, ui->label_4, ui->label_5,
ui->label_6, ui->label_7, ui->label_8, ui->label_9,
};
@@ -115,23 +115,37 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
for (std::size_t i = 0; i < player_tabs.size(); ++i) {
player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
player_tabs[i]->layout()->addWidget(player_controllers[i]);
- connect(player_connected[i], &QCheckBox::clicked, [this, i](int checked) {
- // Ensures that the controllers are always connected in sequential order
- this->propagateMouseClickOnPlayers(i, checked, true);
+ connect(player_controllers[i], &ConfigureInputPlayer::Connected, [this, i](bool checked) {
+ // Ensures that connecting a controller changes the number of players
+ if (connected_controller_checkboxes[i]->isChecked() != checked) {
+ // Ensures that the players are always connected in sequential order
+ PropagatePlayerNumberChanged(i, checked);
+ }
+ });
+ connect(connected_controller_checkboxes[i], &QCheckBox::clicked, [this, i](bool checked) {
+ // Reconnect current controller if it was the last one checked
+ // (player number was reduced by more than one)
+ const bool reconnect_first = !checked &&
+ i < connected_controller_checkboxes.size() - 1 &&
+ connected_controller_checkboxes[i + 1]->isChecked();
+
+ // Ensures that the players are always connected in sequential order
+ PropagatePlayerNumberChanged(i, checked, reconnect_first);
});
connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
&ConfigureInput::UpdateAllInputDevices);
connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this,
&ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection);
- connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) {
+ connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) {
+ // Keep activated controllers synced with the "Connected Controllers" checkboxes
player_controllers[i]->ConnectPlayer(state == Qt::Checked);
});
// Remove/hide all the elements that exceed max_players, if applicable.
if (i >= max_players) {
ui->tabWidget->removeTab(static_cast<int>(max_players));
- player_connected[i]->hide();
- player_connected_labels[i]->hide();
+ connected_controller_checkboxes[i]->hide();
+ connected_controller_labels[i]->hide();
}
}
// Only the first player can choose handheld mode so connect the signal just to player 1
@@ -175,28 +189,25 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
LoadConfiguration();
}
-void ConfigureInput::propagateMouseClickOnPlayers(size_t player_index, bool checked, bool origin) {
- // Origin has already been toggled
- if (!origin) {
- player_connected[player_index]->setChecked(checked);
- }
+void ConfigureInput::PropagatePlayerNumberChanged(size_t player_index, bool checked,
+ bool reconnect_current) {
+ connected_controller_checkboxes[player_index]->setChecked(checked);
if (checked) {
// Check all previous buttons when checked
if (player_index > 0) {
- propagateMouseClickOnPlayers(player_index - 1, checked, false);
+ PropagatePlayerNumberChanged(player_index - 1, checked);
}
} else {
// Unchecked all following buttons when unchecked
- if (player_index < player_tabs.size() - 1) {
- // Reconnect current player if it was the last one checked
- // (player number was reduced by more than one)
- if (origin && player_connected[player_index + 1]->checkState() == Qt::Checked) {
- player_connected[player_index]->setCheckState(Qt::Checked);
- }
- propagateMouseClickOnPlayers(player_index + 1, checked, false);
+ if (player_index < connected_controller_checkboxes.size() - 1) {
+ PropagatePlayerNumberChanged(player_index + 1, checked);
}
}
+
+ if (reconnect_current) {
+ connected_controller_checkboxes[player_index]->setCheckState(Qt::Checked);
+ }
}
QList<QWidget*> ConfigureInput::GetSubTabs() const {
@@ -249,17 +260,17 @@ void ConfigureInput::LoadConfiguration() {
}
void ConfigureInput::LoadPlayerControllerIndices() {
- for (std::size_t i = 0; i < player_connected.size(); ++i) {
+ for (std::size_t i = 0; i < connected_controller_checkboxes.size(); ++i) {
if (i == 0) {
auto* handheld =
system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
if (handheld->IsConnected()) {
- player_connected[i]->setChecked(true);
+ connected_controller_checkboxes[i]->setChecked(true);
continue;
}
}
const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(i);
- player_connected[i]->setChecked(controller->IsConnected());
+ connected_controller_checkboxes[i]->setChecked(controller->IsConnected());
}
}
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index abb7f7089..136cd3a0a 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -56,7 +56,9 @@ private:
void UpdateDockedState(bool is_handheld);
void UpdateAllInputDevices();
void UpdateAllInputProfiles(std::size_t player_index);
- void propagateMouseClickOnPlayers(size_t player_index, bool origin, bool checked);
+ // Enable preceding controllers or disable following ones
+ void PropagatePlayerNumberChanged(size_t player_index, bool checked,
+ bool reconnect_current = false);
/// Load configuration settings.
void LoadConfiguration();
@@ -71,7 +73,8 @@ private:
std::array<ConfigureInputPlayer*, 8> player_controllers;
std::array<QWidget*, 8> player_tabs;
- std::array<QCheckBox*, 8> player_connected;
+ // Checkboxes representing the "Connected Controllers".
+ std::array<QCheckBox*, 8> connected_controller_checkboxes;
ConfigureInputAdvanced* advanced;
Core::System& system;
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index d4df43d73..d3255d2b4 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -75,7 +75,7 @@ public:
void ClearAll();
signals:
- /// Emitted when this controller is connected by the user.
+ /// Emitted when this controller is (dis)connected by the user.
void Connected(bool connected);
/// Emitted when the Handheld mode is selected (undocked with dual joycons attached).
void HandheldStateChanged(bool is_handheld);
@@ -183,9 +183,6 @@ private:
/// Stores a pair of "Connected Controllers" combobox index and Controller Type enum.
std::vector<std::pair<int, Core::HID::NpadStyleIndex>> index_controller_type_pairs;
- static constexpr int PLAYER_COUNT = 8;
- std::array<QCheckBox*, PLAYER_COUNT> player_connected_checkbox;
-
/// This will be the the setting function when an input is awaiting configuration.
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 0783a2430..7049c57b6 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -127,7 +127,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons
return list;
}
- if (thread.GetOwnerProcess() == nullptr || !thread.GetOwnerProcess()->Is64BitProcess()) {
+ if (thread.GetOwnerProcess() == nullptr || !thread.GetOwnerProcess()->Is64Bit()) {
return list;
}
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 2bb1a0239..7e7d8e252 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -380,7 +380,6 @@ void GameList::UnloadController() {
GameList::~GameList() {
UnloadController();
- emit ShouldCancelWorker();
}
void GameList::SetFilterFocus() {
@@ -397,6 +396,10 @@ void GameList::ClearFilter() {
search_field->clear();
}
+void GameList::WorkerEvent() {
+ current_worker->ProcessEvents(this);
+}
+
void GameList::AddDirEntry(GameListDir* entry_items) {
item_model->invisibleRootItem()->appendRow(entry_items);
tree_view->setExpanded(
@@ -826,28 +829,21 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
- // Before deleting rows, cancel the worker so that it is not using them
- emit ShouldCancelWorker();
+ // Cancel any existing worker.
+ current_worker.reset();
// Delete any rows that might already exist if we're repopulating
item_model->removeRows(0, item_model->rowCount());
search_field->clear();
- GameListWorker* worker =
- new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);
+ current_worker = std::make_unique<GameListWorker>(vfs, provider, game_dirs, compatibility_list,
+ play_time_manager, system);
- connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
- connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry,
- Qt::QueuedConnection);
- connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating,
+ // Get events from the worker as data becomes available
+ connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameList::WorkerEvent,
Qt::QueuedConnection);
- // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to
- // cancel without delay.
- connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel,
- Qt::DirectConnection);
- QThreadPool::globalInstance()->start(worker);
- current_worker = std::move(worker);
+ QThreadPool::globalInstance()->start(current_worker.get());
}
void GameList::SaveInterfaceLayout() {
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 712570cea..563a3a35b 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -109,7 +109,6 @@ signals:
void BootGame(const QString& game_path, u64 program_id, std::size_t program_index,
StartGameType type, AmLaunchType launch_type);
void GameChosen(const QString& game_path, const u64 title_id = 0);
- void ShouldCancelWorker();
void OpenFolderRequested(u64 program_id, GameListOpenTarget target,
const std::string& game_path);
void OpenTransferableShaderCacheRequested(u64 program_id);
@@ -138,11 +137,16 @@ private slots:
void OnUpdateThemedIcons();
private:
+ friend class GameListWorker;
+ void WorkerEvent();
+
void AddDirEntry(GameListDir* entry_items);
void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent);
- void ValidateEntry(const QModelIndex& item);
void DonePopulating(const QStringList& watch_list);
+private:
+ void ValidateEntry(const QModelIndex& item);
+
void RefreshGameDirectory();
void ToggleFavorite(u64 program_id);
@@ -165,7 +169,7 @@ private:
QVBoxLayout* layout = nullptr;
QTreeView* tree_view = nullptr;
QStandardItemModel* item_model = nullptr;
- GameListWorker* current_worker = nullptr;
+ std::unique_ptr<GameListWorker> current_worker;
QFileSystemWatcher* watcher = nullptr;
ControllerNavigation* controller_navigation = nullptr;
CompatibilityList compatibility_list;
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 077ced12b..69be21027 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -233,10 +233,53 @@ GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
const PlayTime::PlayTimeManager& play_time_manager_,
Core::System& system_)
: vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_},
- compatibility_list{compatibility_list_},
- play_time_manager{play_time_manager_}, system{system_} {}
+ compatibility_list{compatibility_list_}, play_time_manager{play_time_manager_}, system{
+ system_} {
+ // We want the game list to manage our lifetime.
+ setAutoDelete(false);
+}
+
+GameListWorker::~GameListWorker() {
+ this->disconnect();
+ stop_requested.store(true);
+ processing_completed.Wait();
+}
+
+void GameListWorker::ProcessEvents(GameList* game_list) {
+ while (true) {
+ std::function<void(GameList*)> func;
+ {
+ // Lock queue to protect concurrent modification.
+ std::scoped_lock lk(lock);
+
+ // If we can't pop a function, return.
+ if (queued_events.empty()) {
+ return;
+ }
+
+ // Pop a function.
+ func = std::move(queued_events.back());
+ queued_events.pop_back();
+ }
+
+ // Run the function.
+ func(game_list);
+ }
+}
+
+template <typename F>
+void GameListWorker::RecordEvent(F&& func) {
+ {
+ // Lock queue to protect concurrent modification.
+ std::scoped_lock lk(lock);
-GameListWorker::~GameListWorker() = default;
+ // Add the function into the front of the queue.
+ queued_events.emplace_front(std::move(func));
+ }
+
+ // Data now available.
+ emit DataAvailable();
+}
void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
using namespace FileSys;
@@ -284,9 +327,9 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
GetMetadataFromControlNCA(patch, *control, icon, name);
}
- emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
- program_id, compatibility_list, play_time_manager, patch),
- parent_dir);
+ auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
+ program_id, compatibility_list, play_time_manager, patch);
+ RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
}
}
@@ -360,11 +403,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
const FileSys::PatchManager patch{id, system.GetFileSystemController(),
system.GetContentProvider()};
- emit EntryReady(MakeGameListEntry(physical_name, name,
- Common::FS::GetSize(physical_name), icon,
- *loader, id, compatibility_list,
- play_time_manager, patch),
- parent_dir);
+ auto entry = MakeGameListEntry(
+ physical_name, name, Common::FS::GetSize(physical_name), icon, *loader,
+ id, compatibility_list, play_time_manager, patch);
+
+ RecordEvent(
+ [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
}
} else {
std::vector<u8> icon;
@@ -376,11 +420,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
system.GetContentProvider()};
- emit EntryReady(MakeGameListEntry(physical_name, name,
- Common::FS::GetSize(physical_name), icon,
- *loader, program_id, compatibility_list,
- play_time_manager, patch),
- parent_dir);
+ auto entry = MakeGameListEntry(
+ physical_name, name, Common::FS::GetSize(physical_name), icon, *loader,
+ program_id, compatibility_list, play_time_manager, patch);
+
+ RecordEvent(
+ [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
}
}
} else if (is_dir) {
@@ -399,25 +444,34 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
}
void GameListWorker::run() {
+ watch_list.clear();
provider->ClearAllEntries();
+ const auto DirEntryReady = [&](GameListDir* game_list_dir) {
+ RecordEvent([=](GameList* game_list) { game_list->AddDirEntry(game_list_dir); });
+ };
+
for (UISettings::GameDir& game_dir : game_dirs) {
+ if (stop_requested) {
+ break;
+ }
+
if (game_dir.path == QStringLiteral("SDMC")) {
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir);
- emit DirEntryReady(game_list_dir);
+ DirEntryReady(game_list_dir);
AddTitlesToGameList(game_list_dir);
} else if (game_dir.path == QStringLiteral("UserNAND")) {
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir);
- emit DirEntryReady(game_list_dir);
+ DirEntryReady(game_list_dir);
AddTitlesToGameList(game_list_dir);
} else if (game_dir.path == QStringLiteral("SysNAND")) {
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir);
- emit DirEntryReady(game_list_dir);
+ DirEntryReady(game_list_dir);
AddTitlesToGameList(game_list_dir);
} else {
watch_list.append(game_dir.path);
auto* const game_list_dir = new GameListDir(game_dir);
- emit DirEntryReady(game_list_dir);
+ DirEntryReady(game_list_dir);
ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(),
game_dir.deep_scan, game_list_dir);
ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(),
@@ -425,12 +479,6 @@ void GameListWorker::run() {
}
}
- emit Finished(watch_list);
+ RecordEvent([=](GameList* game_list) { game_list->DonePopulating(watch_list); });
processing_completed.Set();
}
-
-void GameListWorker::Cancel() {
- this->disconnect();
- stop_requested.store(true);
- processing_completed.Wait();
-}
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 54dc05e30..d5990fcde 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -4,6 +4,7 @@
#pragma once
#include <atomic>
+#include <deque>
#include <memory>
#include <string>
@@ -20,6 +21,7 @@ namespace Core {
class System;
}
+class GameList;
class QStandardItem;
namespace FileSys {
@@ -46,24 +48,22 @@ public:
/// Starts the processing of directory tree information.
void run() override;
- /// Tells the worker that it should no longer continue processing. Thread-safe.
- void Cancel();
-
-signals:
+public:
/**
- * The `EntryReady` signal is emitted once an entry has been prepared and is ready
- * to be added to the game list.
- * @param entry_items a list with `QStandardItem`s that make up the columns of the new
- * entry.
+ * Synchronously processes any events queued by the worker.
+ *
+ * AddDirEntry is called on the game list for every discovered directory.
+ * AddEntry is called on the game list for every discovered program.
+ * DonePopulating is called on the game list when processing completes.
*/
- void DirEntryReady(GameListDir* entry_items);
- void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir);
+ void ProcessEvents(GameList* game_list);
- /**
- * After the worker has traversed the game directory looking for entries, this signal is
- * emitted with a list of folders that should be watched for changes as well.
- */
- void Finished(QStringList watch_list);
+signals:
+ void DataAvailable();
+
+private:
+ template <typename F>
+ void RecordEvent(F&& func);
private:
void AddTitlesToGameList(GameListDir* parent_dir);
@@ -84,8 +84,11 @@ private:
QStringList watch_list;
- Common::Event processing_completed;
+ std::mutex lock;
+ std::condition_variable cv;
+ std::deque<std::function<void(GameList*)>> queued_events;
std::atomic_bool stop_requested = false;
+ Common::Event processing_completed;
Core::System& system;
};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 1431cf2fe..0df163029 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -159,8 +159,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/util/clickable_label.h"
#include "yuzu/vk_device_info.h"
-#ifdef YUZU_DBGHELP
-#include "yuzu/mini_dump.h"
+#ifdef YUZU_CRASH_DUMPS
+#include "yuzu/breakpad.h"
#endif
using namespace Common::Literals;
@@ -1072,7 +1072,7 @@ void GMainWindow::InitializeWidgets() {
});
volume_popup->layout()->addWidget(volume_slider);
- volume_button = new QPushButton();
+ volume_button = new VolumeButton();
volume_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
volume_button->setFocusPolicy(Qt::NoFocus);
volume_button->setCheckable(true);
@@ -1103,6 +1103,8 @@ void GMainWindow::InitializeWidgets() {
context_menu.exec(volume_button->mapToGlobal(menu_location));
volume_button->repaint();
});
+ connect(volume_button, &VolumeButton::VolumeChanged, this, &GMainWindow::UpdateVolumeUI);
+
statusBar()->insertPermanentWidget(0, volume_button);
// setup AA button
@@ -2019,7 +2021,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())}
.filename());
}
- const bool is_64bit = system->Kernel().ApplicationProcess()->Is64BitProcess();
+ const bool is_64bit = system->Kernel().ApplicationProcess()->Is64Bit();
const auto instruction_set_suffix = is_64bit ? tr("(64-bit)") : tr("(32-bit)");
title_name = tr("%1 %2", "%1 is the title name. %2 indicates if the title is 64-bit or 32-bit")
.arg(QString::fromStdString(title_name), instruction_set_suffix)
@@ -2906,7 +2908,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
const std::string game_file_name = std::filesystem::path(game_path).filename().string();
// Determine full paths for icon and shortcut
-#if defined(__linux__) || defined(__FreeBSD__)
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
const char* home = std::getenv("HOME");
const std::filesystem::path home_path = (home == nullptr ? "~" : home);
const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
@@ -2963,7 +2965,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
QImage icon_data =
QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
-#if defined(__linux__) || defined(__FreeBSD__)
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
// Convert and write the icon as a PNG
if (!icon_data.save(QString::fromStdString(icon_path.string()))) {
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
@@ -4002,7 +4004,7 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
const std::string& comment, const std::string& icon_path,
const std::string& command, const std::string& arguments,
const std::string& categories, const std::string& keywords) {
-#if defined(__linux__) || defined(__FreeBSD__)
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
// This desktop file template was writing referencing
// https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html
std::string shortcut_contents{};
@@ -5126,6 +5128,32 @@ void GMainWindow::changeEvent(QEvent* event) {
QWidget::changeEvent(event);
}
+void VolumeButton::wheelEvent(QWheelEvent* event) {
+
+ int num_degrees = event->angleDelta().y() / 8;
+ int num_steps = (num_degrees / 15) * scroll_multiplier;
+ // Stated in QT docs: Most mouse types work in steps of 15 degrees, in which case the delta
+ // value is a multiple of 120; i.e., 120 units * 1/8 = 15 degrees.
+
+ if (num_steps > 0) {
+ Settings::values.volume.SetValue(
+ std::min(200, Settings::values.volume.GetValue() + num_steps));
+ } else {
+ Settings::values.volume.SetValue(
+ std::max(0, Settings::values.volume.GetValue() + num_steps));
+ }
+
+ scroll_multiplier = std::min(MaxMultiplier, scroll_multiplier * 2);
+ scroll_timer.start(100); // reset the multiplier if no scroll event occurs within 100 ms
+
+ emit VolumeChanged();
+ event->accept();
+}
+
+void VolumeButton::ResetMultiplier() {
+ scroll_multiplier = 1;
+}
+
#ifdef main
#undef main
#endif
@@ -5187,22 +5215,15 @@ int main(int argc, char* argv[]) {
return 0;
}
-#ifdef YUZU_DBGHELP
- PROCESS_INFORMATION pi;
- if (!is_child && Settings::values.create_crash_dumps.GetValue() &&
- MiniDump::SpawnDebuggee(argv[0], pi)) {
- // Delete the config object so that it doesn't save when the program exits
- config.reset(nullptr);
- MiniDump::DebugDebuggee(pi);
- return 0;
- }
-#endif
-
if (StartupChecks(argv[0], &has_broken_vulkan,
Settings::values.perform_vulkan_check.GetValue())) {
return 0;
}
+#ifdef YUZU_CRASH_DUMPS
+ Breakpad::InstallCrashHandler();
+#endif
+
Common::DetachedTasks detached_tasks;
MicroProfileOnThreadCreate("Frontend");
SCOPE_EXIT({ MicroProfileShutdown(); });
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 270a40c5f..f9c6efe4f 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -8,6 +8,7 @@
#include <QMainWindow>
#include <QMessageBox>
+#include <QPushButton>
#include <QTimer>
#include <QTranslator>
@@ -137,6 +138,28 @@ namespace VkDeviceInfo {
class Record;
}
+class VolumeButton : public QPushButton {
+ Q_OBJECT
+public:
+ explicit VolumeButton(QWidget* parent = nullptr) : QPushButton(parent), scroll_multiplier(1) {
+ connect(&scroll_timer, &QTimer::timeout, this, &VolumeButton::ResetMultiplier);
+ }
+
+signals:
+ void VolumeChanged();
+
+protected:
+ void wheelEvent(QWheelEvent* event) override;
+
+private slots:
+ void ResetMultiplier();
+
+private:
+ int scroll_multiplier;
+ QTimer scroll_timer;
+ constexpr static int MaxMultiplier = 8;
+};
+
class GMainWindow : public QMainWindow {
Q_OBJECT
@@ -481,7 +504,7 @@ private:
QPushButton* dock_status_button = nullptr;
QPushButton* filter_status_button = nullptr;
QPushButton* aa_status_button = nullptr;
- QPushButton* volume_button = nullptr;
+ VolumeButton* volume_button = nullptr;
QWidget* volume_popup = nullptr;
QSlider* volume_slider = nullptr;
QTimer status_bar_update_timer;
diff --git a/src/yuzu/mini_dump.cpp b/src/yuzu/mini_dump.cpp
deleted file mode 100644
index a34dc6a9c..000000000
--- a/src/yuzu/mini_dump.cpp
+++ /dev/null
@@ -1,202 +0,0 @@
-// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <cstdio>
-#include <cstring>
-#include <ctime>
-#include <filesystem>
-#include <fmt/format.h>
-#include <windows.h>
-#include "yuzu/mini_dump.h"
-#include "yuzu/startup_checks.h"
-
-// dbghelp.h must be included after windows.h
-#include <dbghelp.h>
-
-namespace MiniDump {
-
-void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
- EXCEPTION_POINTERS* pep) {
- char file_name[255];
- const std::time_t the_time = std::time(nullptr);
- std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time));
-
- // Open the file
- HANDLE file_handle = CreateFileA(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
- CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
-
- if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) {
- fmt::print(stderr, "CreateFileA failed. Error: {}", GetLastError());
- return;
- }
-
- // Create the minidump
- const MINIDUMP_TYPE dump_type = MiniDumpNormal;
-
- const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle,
- dump_type, (pep != 0) ? info : 0, 0, 0);
-
- if (write_dump_status) {
- fmt::print(stderr, "MiniDump created: {}", file_name);
- } else {
- fmt::print(stderr, "MiniDumpWriteDump failed. Error: {}", GetLastError());
- }
-
- // Close the file
- CloseHandle(file_handle);
-}
-
-void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) {
- EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
-
- HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId);
- if (thread_handle == nullptr) {
- fmt::print(stderr, "OpenThread failed ({})", GetLastError());
- return;
- }
-
- // Get child process context
- CONTEXT context = {};
- context.ContextFlags = CONTEXT_ALL;
- if (!GetThreadContext(thread_handle, &context)) {
- fmt::print(stderr, "GetThreadContext failed ({})", GetLastError());
- return;
- }
-
- // Create exception pointers for minidump
- EXCEPTION_POINTERS ep;
- ep.ExceptionRecord = &record;
- ep.ContextRecord = &context;
-
- MINIDUMP_EXCEPTION_INFORMATION info;
- info.ThreadId = deb_ev.dwThreadId;
- info.ExceptionPointers = &ep;
- info.ClientPointers = false;
-
- CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep);
-
- if (CloseHandle(thread_handle) == 0) {
- fmt::print(stderr, "error: CloseHandle(thread_handle) failed ({})", GetLastError());
- }
-}
-
-bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) {
- std::memset(&pi, 0, sizeof(pi));
-
- // Don't debug if we are already being debugged
- if (IsDebuggerPresent()) {
- return false;
- }
-
- if (!SpawnChild(arg0, &pi, 0)) {
- fmt::print(stderr, "warning: continuing without crash dumps");
- return false;
- }
-
- const bool can_debug = DebugActiveProcess(pi.dwProcessId);
- if (!can_debug) {
- fmt::print(stderr,
- "warning: DebugActiveProcess failed ({}), continuing without crash dumps",
- GetLastError());
- return false;
- }
-
- return true;
-}
-
-static const char* ExceptionName(DWORD exception) {
- switch (exception) {
- case EXCEPTION_ACCESS_VIOLATION:
- return "EXCEPTION_ACCESS_VIOLATION";
- case EXCEPTION_DATATYPE_MISALIGNMENT:
- return "EXCEPTION_DATATYPE_MISALIGNMENT";
- case EXCEPTION_BREAKPOINT:
- return "EXCEPTION_BREAKPOINT";
- case EXCEPTION_SINGLE_STEP:
- return "EXCEPTION_SINGLE_STEP";
- case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
- return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
- case EXCEPTION_FLT_DENORMAL_OPERAND:
- return "EXCEPTION_FLT_DENORMAL_OPERAND";
- case EXCEPTION_FLT_DIVIDE_BY_ZERO:
- return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
- case EXCEPTION_FLT_INEXACT_RESULT:
- return "EXCEPTION_FLT_INEXACT_RESULT";
- case EXCEPTION_FLT_INVALID_OPERATION:
- return "EXCEPTION_FLT_INVALID_OPERATION";
- case EXCEPTION_FLT_OVERFLOW:
- return "EXCEPTION_FLT_OVERFLOW";
- case EXCEPTION_FLT_STACK_CHECK:
- return "EXCEPTION_FLT_STACK_CHECK";
- case EXCEPTION_FLT_UNDERFLOW:
- return "EXCEPTION_FLT_UNDERFLOW";
- case EXCEPTION_INT_DIVIDE_BY_ZERO:
- return "EXCEPTION_INT_DIVIDE_BY_ZERO";
- case EXCEPTION_INT_OVERFLOW:
- return "EXCEPTION_INT_OVERFLOW";
- case EXCEPTION_PRIV_INSTRUCTION:
- return "EXCEPTION_PRIV_INSTRUCTION";
- case EXCEPTION_IN_PAGE_ERROR:
- return "EXCEPTION_IN_PAGE_ERROR";
- case EXCEPTION_ILLEGAL_INSTRUCTION:
- return "EXCEPTION_ILLEGAL_INSTRUCTION";
- case EXCEPTION_NONCONTINUABLE_EXCEPTION:
- return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
- case EXCEPTION_STACK_OVERFLOW:
- return "EXCEPTION_STACK_OVERFLOW";
- case EXCEPTION_INVALID_DISPOSITION:
- return "EXCEPTION_INVALID_DISPOSITION";
- case EXCEPTION_GUARD_PAGE:
- return "EXCEPTION_GUARD_PAGE";
- case EXCEPTION_INVALID_HANDLE:
- return "EXCEPTION_INVALID_HANDLE";
- default:
- return "unknown exception type";
- }
-}
-
-void DebugDebuggee(PROCESS_INFORMATION& pi) {
- DEBUG_EVENT deb_ev = {};
-
- while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) {
- const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE);
- if (!wait_success) {
- fmt::print(stderr, "error: WaitForDebugEvent failed ({})", GetLastError());
- return;
- }
-
- switch (deb_ev.dwDebugEventCode) {
- case OUTPUT_DEBUG_STRING_EVENT:
- case CREATE_PROCESS_DEBUG_EVENT:
- case CREATE_THREAD_DEBUG_EVENT:
- case EXIT_PROCESS_DEBUG_EVENT:
- case EXIT_THREAD_DEBUG_EVENT:
- case LOAD_DLL_DEBUG_EVENT:
- case RIP_EVENT:
- case UNLOAD_DLL_DEBUG_EVENT:
- // Continue on all other debug events
- ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE);
- break;
- case EXCEPTION_DEBUG_EVENT:
- EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
-
- // We want to generate a crash dump if we are seeing the same exception again.
- if (!deb_ev.u.Exception.dwFirstChance) {
- fmt::print(stderr, "Creating MiniDump on ExceptionCode: 0x{:08x} {}\n",
- record.ExceptionCode, ExceptionName(record.ExceptionCode));
- DumpFromDebugEvent(deb_ev, pi);
- }
-
- // Continue without handling the exception.
- // Lets the debuggee use its own exception handler.
- // - If one does not exist, we will see the exception once more where we make a minidump
- // for. Then when it reaches here again, yuzu will probably crash.
- // - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an
- // infinite loop of exceptions.
- ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
- break;
- }
- }
-}
-
-} // namespace MiniDump
diff --git a/src/yuzu/mini_dump.h b/src/yuzu/mini_dump.h
deleted file mode 100644
index d6b6cca84..000000000
--- a/src/yuzu/mini_dump.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <windows.h>
-
-#include <dbghelp.h>
-
-namespace MiniDump {
-
-void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
- EXCEPTION_POINTERS* pep);
-
-void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi);
-bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi);
-void DebugDebuggee(PROCESS_INFORMATION& pi);
-
-} // namespace MiniDump
diff --git a/vcpkg.json b/vcpkg.json
index 203fc9708..da4e9edb9 100644
--- a/vcpkg.json
+++ b/vcpkg.json
@@ -33,10 +33,6 @@
"description": "Compile tests",
"dependencies": [ "catch2" ]
},
- "dbghelp": {
- "description": "Compile Windows crash dump (Minidump) support",
- "dependencies": [ "dbghelp" ]
- },
"web-service": {
"description": "Enable web services (telemetry, etc.)",
"dependencies": [